---
title: "Deploy a Streamlit app"
description: "Deploy a Streamlit app on Lizard: a Procfile runs streamlit run app.py on 0.0.0.0:8501, requirements.txt pins Streamlit, secrets come from variables."
url: https://lizard.build/docs/framework-guides/streamlit/
updated: 2026-10-12
---

# Deploy a Streamlit app

Lizard runs a Streamlit app with the command you use locally, `streamlit run app.py`. Put it in a Procfile `web:` line with `--server.address 0.0.0.0` and a fixed port such as `8501`, then deploy with `lizard up --port 8501`. Without the Procfile, lizardpack starts `python app.py`, which prints a warning and exits instead of serving the app.

**Test status:** the sample below ran locally with Streamlit 1.65.0, and the database version with psycopg 3.3.5 against PostgreSQL 14. We checked its build plan with lizardpack, the build planner Lizard runs. We have not yet deployed it end to end. The browser keeps a WebSocket open to Streamlit, and we have not yet checked that connection through Lizard's routing.

## Before you start

- Python 3 on your computer to test the app.
- Lizard CLI, signed in with `lizard login`, on Pro or the 7-day Pro trial (card required). See [pricing](https://lizard.build/pricing).

## A minimal app

`app.py`:

```python
import streamlit as st

st.title("Hello from Streamlit on Lizard")
name = st.text_input("Your name", "world")
st.write(f"Hello, {name}!")
```

`requirements.txt`:

```text
streamlit==1.65.0
```

`.python-version`:

```text
3.13
```

`Procfile`:

```text
web: streamlit run app.py --server.port 8501 --server.address 0.0.0.0
```

Run it locally:

```bash
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
streamlit run app.py
```

Streamlit opens on `http://localhost:8501`. Add `.venv/`, `.env` and `.streamlit/secrets.toml` to `.gitignore`.

## What lizardpack builds

| Step | What happens |
|---|---|
| Detection | `requirements.txt`, `pyproject.toml`, `Pipfile` or any `.py` file |
| Python version | `.python-version`, then `.tool-versions`, then `requires-python` in `pyproject.toml`; major and minor only, on `python:<x.y>-slim`. Default `3.13`. |
| Install | `pip install -r requirements.txt`; uv, Poetry or Pipenv when their lock or config file is present |
| Start | The Procfile `web:` line |
| Runtime | `PYTHONUNBUFFERED=1`, so `print` output reaches the logs at once |

Set the service port with `--port` to match `--server.port` in the Procfile. See [runtime versions](https://lizard.build/docs/deploy/runtime-versions.md) for the version rules and their edge cases.

## Deploy

From the app's folder:

```bash
lizard init --name streamlit-app
lizard add --service web
lizard up --service web --port 8501
```

`lizard up` uploads the folder, builds it on Lizard and prints the URL. Inside a git repository it skips what `.gitignore` lists; outside git it uploads `.env` files. For git push to deploy, [connect a GitHub repository](https://lizard.build/docs/deploy/github.md) and set the port with `lizard port 8501 --service web`.

## Secrets and settings

`st.secrets` reads `.streamlit/secrets.toml`, a file you should not upload. On Lizard, store secrets as service variables and read them from the environment:

```bash
lizard secrets set OPENAI_API_KEY=sk-... --service web
```

```python
import os

api_key = os.environ["OPENAI_API_KEY"]
```

A variable change restarts the app without a rebuild. See [variables and secrets](https://lizard.build/docs/variables.md).

## Add Managed Postgres

```bash
lizard add postgres
lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service web
```

The single quotes keep `${{…}}` away from your shell; Lizard resolves the [reference](https://lizard.build/docs/variables/references.md) at deploy time. Add `psycopg[binary]==3.3.5` to `requirements.txt` and open one connection per server process:

```python
import os

import psycopg
import streamlit as st


@st.cache_resource
def get_conn():
    return psycopg.connect(os.environ["DATABASE_URL"], autocommit=True)


with get_conn().cursor() as cur:
    cur.execute("SELECT now()")
    st.write("Database time:", cur.fetchone()[0])
```

`st.cache_resource` keeps the connection across reruns and browser sessions. Keep data in [Managed Postgres](https://lizard.build/docs/addons/postgres.md) or [Managed Object Storage](https://lizard.build/docs/addons/storage.md): files the app writes inside the container are lost on the next deploy.

## Check that it works

```bash
lizard logs --service web --tail 100
lizard domain --service web
curl https://<your-domain>/_stcore/health
```

The log shows `You can now view your Streamlit app in your browser`, and the health URL answers `ok`. Open the app's URL and change the text box: the greeting should update.

## Common problems

| Symptom | Fix |
|---|---|
| Log says `to view this Streamlit app on a browser, run it with the following command` | The Procfile is missing or has no `web:` line, so lizardpack ran `python app.py`. |
| Deploy never becomes healthy | The service port and `--server.port` differ. Run `lizard port --service web`. See [service never healthy](https://lizard.build/docs/deploy/troubleshooting/service-never-healthy.md). |
| `ModuleNotFoundError` | Add the package to `requirements.txt` and deploy again. |
| `KeyError` for a secret | The variable is missing on the `web` service: `lizard secrets list --service web`. |
| The page shows a connecting message and never loads | The browser cannot keep its WebSocket to `/_stcore/stream`. Look for that failed request in the browser console, and for errors in `lizard logs`. |

## Related

- [Framework guides](https://lizard.build/docs/framework-guides.md)
- [Deploy FastAPI](https://lizard.build/docs/framework-guides/fastapi.md)
- [Runtime versions](https://lizard.build/docs/deploy/runtime-versions.md)
- [Managed Postgres](https://lizard.build/docs/addons/postgres.md)
- [Logs](https://lizard.build/docs/observability/logs.md)
