Files
rootnode-academy/infra/docker/IMAGE_STRATEGY.md
T
ced4568 d2280d0f0d feat(security): production Compose topology, firewall rulesets, Docker mTLS
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).
2026-08-18 17:13:09 -05:00

50 lines
2.4 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).