platform/platform-infra/stacks/README.md
Bart Van Geyt 41a01c60ef Phase 2: platform service stacks + CLAUDE.md
Add base Docker Compose projects under platform-infra/stacks, using the
Phase 1 ZFS platform datasets and shared networks:

- bootstrap-networks.sh: idempotent creation of shared `proxy` (edge) and
  `platform` (internal) Docker networks.
- traefik: edge router with TLS/ACME (HTTP-01), global HTTP->HTTPS redirect,
  file-based security-headers/TLS middlewares, and a basic-auth dashboard.
  Docker access via a least-privilege tecnativa/docker-socket-proxy instead
  of mounting docker.sock directly.
- mariadb: shared instance on tank/platform/mariadb, utf8mb4, tuned; not
  published to the host (internal `platform` network only).
- forgejo: Git + container registry over HTTPS (SSH disabled to avoid extra
  inbound ports), SQLite backend, data on tank/platform/forgejo.

Each stack ships a committed .env.example (real .env is git-ignored) and the
stacks README documents bring-up order, secrets, and verification.

Also add root CLAUDE.md so fresh sessions orient from files cheaply:
philosophy, doc pointers, naming quick-ref, repo map, commands, agreements.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:58:18 +02:00

58 lines
2.6 KiB
Markdown

# 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.