Named-bridge-network production Compose (not host networking, unlike dev) for rn-platform-01; egress-proxy + agents for rn-lab-host-01. Caddy reverse proxy, single public origin, Grafana restricted to management_cidr. iptables-restore firewall rulesets for all three hosts (Proxmox host, rn-platform-01, rn-lab-host-01) codifying the Platform/Lab-Host boundary: no student-to-student traffic, no direct Proxmox exposure, no direct Postgres/Provisioner exposure from the Lab Host. Mutual TLS for the Provisioner<->Lab Host Docker connection: certificate generation/verification scripts, dockerd TLS config, firewall-restricted port 2376. Application support already existed since Phase 1; this is purely the operational half. Validated: docker compose config, caddy validate, firewall structural lint, and a real openssl s_server/s_client mTLS handshake proving the cert chain and mutual-auth rejection (legitimate client verified, rogue client correctly rejected with verify code 19).
50 lines
2.4 KiB
Markdown
50 lines
2.4 KiB
Markdown
# Container image strategy
|
||
|
||
## Images
|
||
|
||
| Image | Built from | Registry path |
|
||
|---|---|---|
|
||
| `rootnode/platform-api` | `platform/api/Dockerfile` | `{{ registry }}/rootnode/platform-api` |
|
||
| `rootnode/gateway` | `platform/gateway/Dockerfile` | `{{ registry }}/rootnode/gateway` |
|
||
| `rootnode/provisioner` | `platform/provisioner/Dockerfile` | `{{ registry }}/rootnode/provisioner` |
|
||
| `rootnode/egress-proxy` | `platform/egress-proxy/Dockerfile` | `{{ registry }}/rootnode/egress-proxy` |
|
||
| `rootnode/lab` | root `Dockerfile` (the student Lab image) | `{{ registry }}/rootnode/lab` |
|
||
|
||
## Tagging
|
||
|
||
**Never `:latest` in staging or production.** Every build is tagged with:
|
||
|
||
- the full git SHA (`sha-<40 hex>`) — always, every build, the ground truth
|
||
- `staging-<short-sha>` on every push to `main` that passes CI (moving pointer, staging only)
|
||
- a semantic tag (`vX.Y.Z`) only on an explicit, human-triggered production release
|
||
|
||
`infra/scripts/ci/tag-and-push.sh` implements this — see §18 in
|
||
`docs/PHASE4_PRODUCTION_INFRASTRUCTURE.md`.
|
||
|
||
Deployment configs (`infra/environments/*/*.env.template`, Ansible `group_vars`) reference the
|
||
tag as a variable (`image_tag`), never a hardcoded value baked into a compose file — rolling
|
||
back is changing one variable and re-running the `platform`/`lab-host` Ansible role, not editing
|
||
a compose file by hand (see `docs/RUNBOOK_ROLLBACK.md`).
|
||
|
||
## Registry
|
||
|
||
No paid registry required. Two supported options, chosen at deploy time via `registry` in
|
||
`infra/ansible/group_vars/production.yml`:
|
||
|
||
1. **Gitea's own built-in container registry** (`git.synthossystems.com`) — zero new
|
||
infrastructure, already authenticated the same way the repo itself is. Preferred default.
|
||
2. **A self-hosted registry** (e.g. a `registry:2` container on rn-platform-01 itself, or
|
||
elsewhere in the homelab) — a documented fallback if the Gitea registry isn't enabled on
|
||
this instance.
|
||
|
||
Either way: private registry, credentials pulled the same way any other production secret is
|
||
(`docs/PRODUCTION_DEPLOYMENT.md` §10), never a public Docker Hub push of anything beyond what's
|
||
already public (this project doesn't publish anything to Docker Hub in Phase 4).
|
||
|
||
## Rollback
|
||
|
||
Every image is content-addressable by SHA tag, so "roll back" is always available as long as
|
||
the previous tag hasn't been garbage-collected from the registry — see
|
||
`docs/RUNBOOK_ROLLBACK.md` for the exact procedure (re-point `image_tag`, re-run the Ansible
|
||
role, verify).
|