Files
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

2.5 KiB
Raw Permalink Blame History

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