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>
58 lines
2.6 KiB
Markdown
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.
|