# StemAtlas Deployment — Implementation Plan **Design:** `thoughts/shared/designs/2026-03-22-stematlas-deployment-design.md` **Date:** 2026-03-22 --- ## Overview Four independent batches. Batches A and B can run in parallel. Batch C requires the pipeline to finish first. Batch D is VPS infrastructure (manual steps, done once). ``` Batch A: stemwijzer repo — Streamlit multi-page + Docker Batch B: sgeboers.nl repo — blog/, nav, blog post HTML skeleton Batch C: Charts — generate + embed (after pipeline finishes) Batch D: VPS infrastructure — Nginx vhost + Certbot + /srv/stematlas/ ``` --- ## Batch A — stemwijzer repo: Streamlit multi-page + Docker ### A1. Check Dockerfile Read existing `Dockerfile` — verify it installs all deps from `pyproject.toml` and sets `CMD` to start the app. Note current entrypoint (probably `streamlit run app.py`). ### A2. Create `Home.py` New file at project root. Streamlit landing/about page: - Title: "StemAtlas" - Brief description of the two pages (quiz + explorer) - Links (Streamlit sidebar nav handles the rest automatically) - `st.page_link()` cards pointing to the two pages ### A3. Create `pages/1_Stemwijzer.py` Thin wrapper that imports and calls `app.main()`: - Import `from app import main` - Remove the `if __name__ == "__main__": main()` guard from `app.py` (or keep it — Streamlit ignores it when the file is imported) - The page title shown in Streamlit nav comes from the filename: `1_Stemwijzer` → "Stemwijzer" ### A4. Create `pages/2_Explorer.py` Same pattern: - Import `from explorer import run_app` - Call `run_app()` - Filename → nav label: "Explorer" ### A5. Update Dockerfile CMD Change entrypoint from `streamlit run app.py` to `streamlit run Home.py --server.port 8501 --server.address 0.0.0.0`. ### A6. Create `docker-compose.yml` Two services in the stemwijzer repo: ```yaml version: "3.9" services: stematlas: image: ${DOCKER_REGISTRY}/sgeboers/stemwijzer:latest ports: - "127.0.0.1:8501:8501" volumes: - /srv/stematlas/data:/app/data restart: unless-stopped environment: - DB_PATH=/app/data/motions.db scheduler: image: ${DOCKER_REGISTRY}/sgeboers/stemwijzer:latest command: python scheduler.py volumes: - /srv/stematlas/data:/app/data restart: unless-stopped environment: - DB_PATH=/app/data/motions.db ``` `127.0.0.1:8501` — only accessible from localhost, Nginx proxies externally. ### A7. Smoke test for `Home.py` Add `tests/test_home_import.py` — same pattern as `test_explorer_import.py`. Verify `Home` module is importable, `run_app` or equivalent callable exists. ### A8. Run tests `.venv/bin/python -m pytest -q` — all existing + new smoke tests must pass. ### Verification `docker build -t stematlas-local .` locally to confirm image builds without errors. --- ## Batch B — sgeboers.nl repo: blog/ + nav > This batch requires access to the sgeboers.nl repo on git.sgeboers.nl. > Steps below assume the repo is cloned locally. ### B1. Inspect existing site structure Read `index.html` and any existing CSS files to understand: - Current nav structure (header? sidebar? footer?) - CSS class conventions for links/sections - Any existing page patterns to copy for the blog post ### B2. Create `blog/` directory Add `blog/index.html` — a minimal blog listing page: - Title: "Blog" - One entry: "StemAtlas — Mapping Dutch Democracy" → `blog/stematlas.html` - Matches existing site style ### B3. Add nav link to main site Update `index.html` (or whichever file contains the nav) to add a "Blog" link pointing to `/blog/`. ### B4. Create `blog/stematlas.html` skeleton Full blog post HTML based on `thoughts/blog-post-political-compass.md`: - Convert markdown to HTML (headings, paragraphs, code blocks, tables) - Add Plotly CDN `