# Phase 2 — Platform services Base Docker Compose projects that every customer site depends on. Each is its own compose project on the host, using data directories on the ZFS platform datasets created in Phase 1. | Stack | Role | Network(s) | Data | |-------|------|------------|------| | `traefik` | Edge router: TLS/ACME, dynamic routing, dashboard. Talks to Docker via a **socket-proxy** (least privilege). | `proxy`, `socketproxy` | `tank/platform/traefik` | | `mariadb` | Shared database. Per-site DB + user created later by the CLI. Not published to the host. | `platform` | `tank/platform/mariadb` | | `forgejo` | Git + container registry, git over HTTPS (via Traefik). | `proxy` | `tank/platform/forgejo` | ## Prerequisites - Phase 1 applied (ZFS datasets, Docker with the `zfs` driver, firewall). - DNS A/AAAA records for the platform hostnames (e.g. `traefik.example.com`, `git.example.com`) pointing at the host, so ACME can issue certificates. ## Bring-up order ```bash # 1. Shared networks (once) ./bootstrap-networks.sh # 2. Traefik (edge first, so ACME/routing is ready) cd traefik && cp .env.example .env && $EDITOR .env && docker compose up -d && cd .. # 3. Shared MariaDB cd mariadb && cp .env.example .env && $EDITOR .env && docker compose up -d && cd .. # 4. Forgejo cd forgejo && cp .env.example .env && $EDITOR .env && docker compose up -d && cd .. ``` ## Secrets For now each stack reads a git-ignored `.env` (copy from `.env.example`). Real secrets move to SOPS/age when the provisioning CLI lands (Phase 4). Generate strong values, e.g.: ```bash openssl rand -base64 24 # MariaDB root password htpasswd -nbB admin 'your-password' # Traefik dashboard basic-auth line ``` ## Verify ```bash docker compose ls # all three up curl -I https://traefik.example.com # dashboard (401 = auth working) docker exec -it mariadb mariadb -uroot -p -e 'SELECT VERSION();' curl -I https://git.example.com # Forgejo reachable ``` ## Notes / hardening - **Traefik ↔ Docker via socket-proxy:** Traefik never mounts `docker.sock` directly. `tecnativa/docker-socket-proxy` exposes a read-only, minimal subset on an internal network. This limits blast radius if Traefik is compromised. - **MariaDB is not published** to the host; it is reachable only by containers on the internal `platform` network (sites join it to reach the DB). - **Git over HTTPS only** (SSH disabled) so no extra inbound port is needed — the Phase 1 firewall only opens 80/443 + admin SSH. Enable Forgejo SSH later only if you also open its port in the firewall.