Deploying a Streamlit App on Your VPS (with Cloudflare for the Subdomain)

So you’ve coded a Streamlit project, pushed it to GitHub, and rented a cheap VPS. Here is how to turn that server into a public-facing web app — no prior Docker or sysadmin chops required.

1. Prep the VPS (Ubuntu 22.04)

# connect
ssh root@203.0.113.10        # replace with your real IP

# update & install basics
apt update && apt upgrade -y
apt install python3 python3-pip git nginx -y

# install docker
apt install apt-transport-https ca-certificates curl software-properties-common -y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository \
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt update && apt install docker-ce -y
systemctl enable --now docker

2. Clone Your GitHub Repo

2 a. Create a Personal Access Token

  • GitHub → Settings > Developer settings > Personal access tokens > Generate new token (classic)
  • Select repo scope only → Generate
  • Copy the token (example ghp_XXXXXXXXXXXXXXXX)

2 b. Clone with the Token

# replace USER and TOKEN
git clone https://USER:TOKEN@github.com/USER/your-streamlit-repo.git /root/app
cd /root/app

(Safer: add git config --global credential.helper store, or switch to SSH keys later.)

3. Add a Minimal Dockerfile

# Dockerfile
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8501
CMD ["streamlit","run","app.py","--server.port=8501","--server.address=0.0.0.0"]

No requirements.txt? Auto-generate from imports:

pip install pipreqs
pipreqs /root/app --force

4. Build & Run the Container

docker build -t streamlit-app .
docker run -d -p 8501:8501 --name streamlit --restart unless-stopped streamlit-app

# quick test
curl -I http://localhost:8501   # expect 200 OK

5. Simple Nginx Proxy

nano /etc/nginx/sites-available/app.example.com
server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://localhost:8501;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 300s;
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
    }
}
ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

6. Point a Cloudflare Sub-Domain

  • Cloudflare → DNS → Add an A record:
    app  203.0.113.10 (orange cloud)
  • Cloudflare → SSL/TLS → choose Flexible for quick testing (later switch to Full)

Give DNS a minute, then visit https://app.example.com. Your Streamlit app is live.

7. Pull Updates & Redeploy

cd /root/app
git pull
docker build -t streamlit-app .
docker rm -f streamlit
docker run -d -p 8501:8501 --name streamlit --restart unless-stopped streamlit-app

Automate that block with a script or cron job.

Notes:

A. Streamlit Config Behind Proxy

.streamlit/config.toml in your repo should be:

[server]
enableCORS = false
enableXsrfProtection = false

B. Simple UFW Firewall

ufw allow 22 80 443
ufw enable

C. Optional End-to-End TLS

# switch Cloudflare to "Full (strict)"
apt install certbot python3-certbot-nginx -y
certbot --nginx -d app.example.com