Files
rootnode-academy/infra/docker/IMAGE_STRATEGY.md
T
ced4568 eae8e48d43 feat(routing): Lab Host Router closes the Gateway-to-Lab cross-VM gap
Adds platform/lab-router/ — a narrow service that runs on the Lab Host,
authenticates the Gateway via mTLS, and resolves an opaque routing token to
a local container purely from Docker label state (no Postgres access, no
callback to the Provisioner). Replaces the Gateway's env.internal_ip
assumption (only valid when Gateway and Lab share a Docker daemon) with a
data-driven direct/router decision per lab_host
(lab_hosts.routing_mode/router_endpoint, migration 004), resolved in one
place (platform/gateway/src/lab-endpoint.js's labTarget()) that every
proxy/fetch call site in server.js now goes through.

Extends the existing Docker TLS CA (infra/scripts/docker-tls/generate-docker-tls.sh)
with a second mTLS leaf pair (router-server/gateway-client) for this new
data-plane connection, deployed by the docker-tls Ansible role into a
separate directory from the control-plane pair. Firewall
(rn-lab-host-01.rules) replaces the old speculative wide port-range
allowance with one narrow rule: platform_ip -> the Router's port only.

dev-local stays routing_mode: direct — zero behavior change for local dev,
confirmed by the full existing regression suite passing unmodified.
2026-08-24 20:14:39 -05:00

51 lines
2.5 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-router` | `platform/lab-router/Dockerfile` (Phase 4.1) | `{{ registry }}/rootnode/lab-router` |
| `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).