# 07 · Deployment DigiServer v2 runs as a **Docker Compose** stack (app + Caddy) with optional remote SSH provisioning of players. --- ## 1. Architecture ```mermaid flowchart LR subgraph Host["Docker Host"] subgraph Net["digiserver-network"] APP["digiserver-app\nFlask + Gunicorn :5000"] CAD["caddy:2-alpine\n:80 / :443"] end VOL1["./data/instance → /app/instance\n(SQLite DBs)"] VOL2["./data/uploads → /app/app/static/uploads"] VOL3["./data/Caddyfile → /etc/caddy/Caddyfile"] VOL4["./data/caddy-data → /data\n./data/caddy-config → /config"] end Browser["Browser"] --> CAD CAD --> APP APP --> VOL1 APP --> VOL2 CAD --> VOL3 CAD --> VOL4 APP -.SSH/rsync.-> Players["Remote player hosts"] ``` --- ## 2. `docker-compose.yml` ### Service `digiserver-app` - **Build:** `.` (Dockerfile, `python:3.13-slim`) - **Ports:** `5000:5000` (direct dev access; also `expose: 5000`) - **Volumes:** `./data/instance:/app/instance`, `./data/uploads:/app/app/static/uploads` - **Env:** `FLASK_ENV=production`, `SECRET_KEY`, `ADMIN_USERNAME`, `ADMIN_PASSWORD` - **Healthcheck:** HTTP GET `http://localhost:5000/` (30s interval, 40s start) - **Restart:** `unless-stopped` ### Service `caddy` - **Image:** `caddy:2-alpine` - **Ports:** `8080:80`, `8443:443` - **Volumes:** `./data/Caddyfile:/etc/caddy/Caddyfile:rw`, `./data/caddy-data:/data`, `./data/caddy-config:/config`, `./data/caddy-logs:/var/log/caddy` - **Depends on:** app (service started) - **Healthcheck:** `wget` on port 80 --- ## 3. Dockerfile | Stage | Content | |---|---| | Base | `python:3.13-slim` | | System deps | `poppler-utils`, `ffmpeg`, `libmagic1`, `sudo`, `fonts-noto-color-emoji`, `LibreOffice` (core/impress/writer), `sshpass`, `openssh-client`, `rsync`, `git` | | Python | `COPY requirements.txt` → `pip install` (cached layer), then `COPY . .` | | App config | `FLASK_APP=app.app:create_app`, `FLASK_ENV=production`, `EXPOSE 5000` | | User | Non-root `appuser` (UID 1000) with **passwordless sudo** limited to `apt-get`, `install_libreoffice.sh`, `install_emoji_fonts.sh` | | Runtime | `HEALTHCHECK` (HTTP 5000), `ENTRYPOINT /docker-entrypoint.sh` | --- ## 4. `docker-entrypoint.sh` 1. Create `/app/instance` and `/app/app/static/uploads`. 2. If `dashboard.db` is missing: create app + `db.create_all()`, then create/update the admin user from `ADMIN_USERNAME` / `ADMIN_PASSWORD`. 3. Start **Gunicorn**: `--bind 0.0.0.0:5000 --workers 4 --timeout 120 app.app:create_app('production')`. --- ## 5. `deploy.sh` (One-Shot Deployment) ``` 1. Validate compose + project; create data/ subdirs; copy nginx configs 2. docker compose up -d + verify containers "Up" 3. Run migration scripts (add_https_config_table, add_player_user_table, add_email_to_https_config, migrate_player_user_global, add_original_filename_to_content) 4. Run /app/https_manager.py enable ⚠ https_manager.py is NOT in the current repo — this step needs attention 5. Verify DB tables via SQLAlchemy inspector 6. caddy validate + https_manager.py status; print access URLs + default creds ``` --- ## 6. HTTPS Setup (Caddy) HTTPS is configured through the **Admin → HTTPS Configuration** page, which: 1. Saves `HTTPSConfig` (hostname, domain, IP, email, port, enabled). 2. `CaddyConfigGenerator.generate_caddyfile(config)` picks a template: - **HTTP-only** → `:80` reverse proxy - **Domain** → automatic Let's Encrypt - **IP** → internal CA self-signed 3. Writes `/etc/caddy/Caddyfile` and reloads Caddy via `POST http://caddy:2019/load`. The current `data/Caddyfile` (HTTP mode) includes: admin API on `0.0.0.0:2019`, `:80` block → `digiserver-app:5000`, 2 GB body limit, gzip, security headers, access log. --- ## 7. `verify-deployment.sh` — Pre/Post-Deployment Checks Sections checked (pass/fail/warn counters): - git status - `.env` / `.env.example` - Docker + Compose versions + `compose config` syntax - Dockerfile best practices (HEALTHCHECK, non-root, slim base) - `requirements.txt` critical packages + versions - migrations directory - **SSL cert expiry** (openssl) - Flask config (`ProductionConfig`, `SESSION_COOKIE_SECURE`) - nginx.conf checks - runtime container health - security best practices > ⚠ Note: the script still references `docker-compose` (v1) and `digiserver-nginx` — the current stack uses Compose v2 + Caddy. --- ## 8. Player Deployment Pipeline ```mermaid sequenceDiagram participant U as Admin participant A as App participant B as Background participant H as Player host U->>A: Add player (name, hostname, password/quickconnect) A->>B: background_player_deployment() B->>B: build/refresh staged code (PLAYER_CODE_DIR) B->>H: sshpass + ssh test B->>H: rsync code (or git clone/pull fallback) B->>H: write config/app_config.json (server_ip, quickconnect, https...) B->>H: temporary passwordless sudo B->>H: ./install.sh && ./start.sh B->>H: remove temp sudoers B-->>A: player.deployment_status = 'deployed' | 'failed' U->>A: poll /players/deployment-status ``` Statuses: `pending → deploying → deployed | failed`, with `last_deployment_at/status/message` persisted on the player row. Receiving player feedback also auto-marks deployment `deployed`. --- ## 9. Ports & Volumes Summary | Item | Value | |---|---| | App port (container) | 5000 | | App port (host, dev) | 5000 | | Caddy HTTP | 8080 → 80 | | Caddy HTTPS | 8443 → 443 | | Caddy admin API | 2019 (container) | | DB volume | `./data/instance` | | Uploads volume | `./data/uploads` | | Caddy data/config/logs | `./data/caddy-*` | --- > Next: [08 · Workflows](08-workflows.md)