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— buildsstreamlit run Home.py --server.port=8501docker-compose.yml—motief+schedulerservices,DATA_DIRenv override.drone.yml— builds image, pushes to registry, SSH-deploys on push tomainHome.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).