You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
motief/thoughts/shared/plans/2026-03-26-motief-deploymen...

4.7 KiB

Deployment Plan: motief.sgeboers.nl

Date: 2026-03-26
Subdomain: motief.sgeboers.nl
Stack: Streamlit · DuckDB · Docker · Nginx · Drone CI
Target: VPS, webapps user at /home/webapps/motief/


What's already ready (no changes needed)

  • Dockerfile — builds streamlit run Home.py --server.port=8501
  • docker-compose.ymlmotief + scheduler services, DATA_DIR env override
  • .drone.yml — builds image, pushes to registry, SSH-deploys on push to main
  • Home.py, pages/1_Stemwijzer.py, pages/2_Explorer.py — all exist

Step A — VPS: one-time directory setup

SSH in as webapps:

mkdir -p /home/webapps/motief/data

Create /home/webapps/motief/.env:

DOCKER_REGISTRY=<your-registry-url>
DOCKER_USERNAME=<registry-user>
DOCKER_PASSWORD=<registry-password>
OPENROUTER_API_KEY=<key>
OPENAI_API_KEY=<key>

Copy docker-compose.yml into place:

# From local machine
scp docker-compose.yml webapps@<vps>:/home/webapps/motief/

Or just clone the repo there and symlink — either works since Drone will overwrite it.


Step B — Transfer the database

From local machine (~4 GB, takes a few minutes):

rsync -avz --progress data/motions.db webapps@<vps>:/home/webapps/motief/data/motions.db

Do this as close to go-live as possible so the data isn't stale on launch.


Step C — DNS

Add an A record in your DNS provider:

stematlas  →  (obsolete, skip)
motief     →  <VPS IPv4>

TTL 300 for the first deploy so you can iterate quickly; bump to 3600 after it's stable.


Step D — Nginx vhost

Create /etc/nginx/sites-available/motief:

server {
    listen 80;
    server_name motief.sgeboers.nl;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name motief.sgeboers.nl;

    ssl_certificate     /etc/letsencrypt/live/motief.sgeboers.nl/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/motief.sgeboers.nl/privkey.pem;

    # Streamlit requires WebSocket upgrade for live updates
    location / {
        proxy_pass         http://127.0.0.1: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_read_timeout 86400;
    }
}

Enable and reload:

sudo ln -s /etc/nginx/sites-available/motief /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step E — TLS cert

sudo certbot --nginx -d motief.sgeboers.nl

(Assumes Certbot is already installed and working for other subdomains.)


Step F — Configure Drone secrets

In the Gitea/Drone repo settings for sgeboers/stemwijzer, add:

Secret Value
DOCKER_REGISTRY Your registry URL
DOCKER_USERNAME Registry login
DOCKER_PASSWORD Registry password
DEPLOY_HOST VPS hostname/IP
DEPLOY_SSH_PORT SSH port (usually 22)
DEPLOY_USER webapps
DEPLOY_PASSWORD webapps SSH password

Step G — First deploy

Option 1 — trigger Drone automatically:

git push origin main

Drone builds → pushes image → SSH into VPS → docker-compose up -d.

Option 2 — manual first deploy (on VPS):

cd /home/webapps/motief
docker-compose pull
docker-compose up -d

Step H — Verify

# On VPS
docker-compose -f /home/webapps/motief/docker-compose.yml logs -f motief

# From local browser
open https://motief.sgeboers.nl

Checklist:

  • Home.py loads with nav to Stemwijzer and Explorer
  • Compass tab renders with correct party positions (GL-PvdA top-left, PVV bottom-right)
  • SVD tab scree plot shows with highlighted top-2 bars
  • Similarity search returns results
  • Scheduler container is running (docker-compose ps)

Ongoing: data updates

The scheduler service runs the weekly pipeline inside the container:

  • Scrapes new motions from the TK OData API
  • Re-embeds new motion text via OpenRouter
  • Updates similarity cache

The motions.db file on the VPS is the single source of truth — it's bind-mounted into both containers. No cron job needed on the host.

If you ever need to force a full re-run:

docker-compose exec scheduler python pipeline/run_pipeline.py --db-path data/motions.db

Dependency order

A (dirs + .env)  ─┐
B (rsync DB)     ─┤─► G (first deploy) ─► H (verify)
C (DNS)          ─┤
D (nginx)        ─┤
E (certbot)      ─┘
F (Drone secrets) ──► future auto-deploys on push to main

Steps A–F can all be done in one SSH session. Total estimated time: 45 minutes (mostly waiting on rsync).