# Deploy Streamlit with the streamlit template

Source: https://docs.quake.ai/resources/deployments/deploy-streamlit-template
Markdown: https://docs.quake.ai/resources/deployments/deploy-streamlit-template.md

---

# Deploy Streamlit with the streamlit template

Stand up [Streamlit](https://streamlit.io), a Python framework for data apps and interactive demos, on a single Quake AI instance using the [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) `streamlit`. You apply the template, open the sample app over the floating IP, replace it with your own Python code, and put a reverse proxy in front so the app runs over HTTPS.

Streamlit hosts internal dashboards and model demos on infrastructure you own. You run it yourself; this is a self-hosted tool you operate, not a managed service.

<Figure size="md" caption="What you'll build: a Streamlit data app on a single instance, with Python code and data on a block volume, served over HTTPS through a Caddy reverse proxy">

```d2
direction: right

dev: You {shape: person}
domain: Your domain\n(DNS A record)
fip: Floating IP
instance: Ubuntu instance {
  caddy: Caddy\nreverse proxy
  app: Streamlit\nPython app
  volume: Block volume\n/opt/streamlit
  caddy -> app: proxies 443 to 8501
  app -> volume: reads code + data
}

dev -> domain: HTTPS app
domain -> fip
fip -> instance.caddy
```

</Figure>

<PricingCompanion
  components={[
    { kind: "template", slug: "streamlit", required: true },
  ]}
/>

## Prerequisites

You need:

- OpenTofu 1.6.0 or later (or Terraform 1.6.0 or later) installed locally.
- Your OpenStack credentials sourced into the shell (`source openrc.sh`). See [the OpenStack CLI guide](/docs/tools/openstack-cli).
- An SSH keypair that already exists in your project. Record its name for the `key_name` variable.
- A copy of the `streamlit` template directory from [the template reference page](/resources/iac-templates/streamlit).
- Your workstation's public IP address, so you can open port 8501 to it for first-boot setup. Find it with `curl -sS https://api.ipify.org`.

A domain is optional for first boot. You add it in step 3 to serve the app over HTTPS.

## Step 1: Set the variables and apply the template

The app listens on port 8501 over plain HTTP. The template's security group restricts port 8501 to `app_allowed_cidr`, which defaults to the private network only, so the raw port stays off the public internet. To reach the app from your workstation for first-boot setup, set `app_allowed_cidr` to your own address.

Copy the template's example variables file and open it:

```bash
cp terraform.tfvars.example terraform.tfvars
```

Set `key_name` to the SSH keypair already in your project, and `app_allowed_cidr` to your workstation's public IP with a `/32` suffix:

```hcl
key_name           = "YOUR_KEY_NAME"
app_allowed_cidr   = "YOUR_IP/32"
```



If you would rather not expose port 8501 at all, leave `app_allowed_cidr` at its default and reach the app over an SSH tunnel instead: `ssh -L 8501:localhost:8501 ubuntu@YOUR_FLOATING_IP`, then open `http://localhost:8501`. Once you add a domain in step 3, Caddy serves the app over HTTPS on port 443 and you no longer need port 8501 open.



Initialize the working directory, preview the plan, and apply:

```bash
tofu init
tofu plan
tofu apply
```

OpenTofu provisions a private network, a router, a security group, a block volume mounted at `/opt/streamlit`, an instance, and a floating IP. On first boot, cloud-init mounts the data volume, installs Docker Engine, and starts Streamlit from a compose file on port 8501.

When the apply finishes, read the outputs:

```bash
tofu output
```

Record `floating_ip` and `app_url`.

## Step 2: Open the sample app and deploy your own code

cloud-init takes a few minutes after the instance reaches `ACTIVE`. Open `app_url` (for example `http://YOUR_FLOATING_IP:8501`) in your browser. If the page does not load yet, wait and retry; you can watch the container start over SSH:

```bash
ssh ubuntu@YOUR_FLOATING_IP "sudo docker ps --filter name=streamlit"
```

The sample app shows a slider widget. Replace it with your own project:

1. On your workstation, create a directory with `main.py` and `requirements.txt` for your app.
2. Copy the files to the instance:

```bash
scp -r ./my-app/* ubuntu@YOUR_FLOATING_IP:/opt/streamlit/app/
```

3. Restart the container:

```bash
ssh ubuntu@YOUR_FLOATING_IP "cd /opt/streamlit && sudo docker compose up -d"
```

Store datasets under `/opt/streamlit/data` on the instance. Quake AI flavors are CPU-only; keep in-memory workloads within the instance RAM or read larger files from [Object Storage](/docs/storage/object).



Until you attach a domain in step 3, the app is served over unencrypted HTTP on port 8501, reachable only from `app_allowed_cidr`. Avoid sending production credentials over it from a shared or public network. Adding a domain (step 3) moves the app to HTTPS on port 443.



## Step 3: Serve the app over HTTPS with Caddy

The template leaves ports 80 and 443 open for a reverse proxy. [Caddy](https://caddyserver.com) obtains and renews a TLS certificate automatically once a domain resolves to the instance.

1. Create a DNS **A record** for your domain (for example `data.example.com`) pointing at `YOUR_FLOATING_IP`. Follow [How to point a domain at a Quake AI resource](/docs/network/how-to/point-domain-to-quake-ai). Wait until the record resolves:

```bash
dig +short data.example.com
```

The command returns your floating IP once the record propagates.

2. SSH to the instance and add a Caddy service that proxies HTTPS to Streamlit on port 8501. Create `/opt/streamlit/Caddyfile`:

```text
data.example.com {
  reverse_proxy 127.0.0.1:8501
}
```

3. Add Caddy to the compose file at `/opt/streamlit/docker-compose.yml` so it runs alongside Streamlit:

```yaml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    network_mode: host
    volumes:
      - /opt/streamlit/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
volumes:
  caddy_data:
```

4. Apply the changes and confirm both containers run:

```bash
cd /opt/streamlit
sudo docker compose up -d
sudo docker compose ps
```

Open `https://data.example.com` and confirm the padlock. Streamlit does not ship authentication; add auth at the reverse proxy or in your app if the dashboard should not be public. Once HTTPS works, close direct access to port 8501 by setting `app_allowed_cidr` back to the private network in `terraform.tfvars` and running `tofu apply`.

## What you built

- **Applied the `streamlit` template** to provision a network, security group, data volume, instance, and floating IP, and let cloud-init install Docker and start Streamlit
- **Replaced the sample app** with your own Python code and dependencies on the attached volume
- **Served the app over HTTPS** by pointing a domain at the floating IP and routing it through a Caddy reverse proxy

## Scope of this deployment

This template runs a single-VM Streamlit host, not a managed app platform. The instance is CPU-only and runs in one region. You operate the instance, Docker, your Python code, and the data volume yourself: back them up, patch them, and watch resource use as datasets grow. [Gradio](https://gradio.app) fits the same host shape if your team prefers Gradio widgets: swap the container image and entrypoint while keeping the volume layout.

## Next steps

- [Streamlit template](/resources/iac-templates/streamlit): the template reference, parameters, and resource map
- [Object Storage guides](/docs/storage/object): store large datasets outside the instance volume
- [self-managed PostgreSQL template](/resources/iac-templates/self-managed-postgres): query a warehouse from your Streamlit app
- [Security hardening checklist](/docs/security/hardening-checklist): tighten SSH access and exposure before you serve real traffic

## Clean up

When you no longer need the deployment, destroy everything the template created:

```bash
tofu destroy
```

Then remove the DNS A record you created in step 3. Because your app code and data live on the instance and its attached volume, `tofu destroy` removes them along with the infrastructure. Copy anything you want to keep off the volume before you destroy.
