# Contract — CONTRACT_003 — Container Network, DNS, Ports & Volumes **Between**: all `bootstrap/components/*` that create containers ↔ each other (service discovery) **Status**: Agreed (pending implementation validation) **Realizes**: PLAN-002 §0 (Layer-0 = containers), §3 · **Uses**: `packages/pulumi-docker` (`DockerDeployments`) ## Interface ### 3.1 Network - **Name**: `foundation-net` (Docker user-defined bridge — enables name-based DNS). - **Subnet**: `172.30.0.0/24` (configurable, CONTRACT_001 `network.subnet`). - **DNS**: containers reach each other by **container name** on `foundation-net`. No hardcoded IPs. ### 3.2 Containers, ports, exposure | Container name | Image (digest in VERSIONS) | Internal port(s) | Published to host? | Reached by | |----------------|----------------------------|------------------|--------------------|------------| | `foundation-caddy` | caddy | 80, 443 | **Yes** 80/443 | the internet | | `foundation-forgejo` | forgejo | 3000 (http), 22 (sshd) | SSH **yes** as `:2222`; HTTP **no** (via Caddy) | Caddy → 3000; git over `:2222` | | `foundation-postgres` | postgres | 5432 | **No** (internal only) | forgejo | | `foundation-rustfs` | rustfs | 9000 (S3 API), 9001 (console) | optional (S3 via Caddy) | forgejo, backup | | `foundation-vault` | vault | 8200 | **No** (via Caddy, restricted) | pulumi, components | | `foundation-runner` | act_runner | — (egress only) | **No** | registers to forgejo | | `foundation-registry-cache` | registry:2 | 5000 | **No** (internal only) | runner (Docker Hub pull-through) | **Exposure rule**: only Caddy publishes 80/443; Forgejo SSH is the one extra published port (`:2222`). Everything else is **internal to `foundation-net`** (PLAN-002 §9.4). The runner SHOULD run on a **separate privileged VM/network** (PLAN-001 §4a) — if co-located, fence it (NetworkPolicy-equivalent). ### 3.3 Internal endpoints (what components write into config/app.ini) ``` postgres: foundation-postgres:5432 rustfs (S3): http://foundation-rustfs:9000 vault: http://foundation-vault:8200 forgejo (http): foundation-forgejo:3000 registry cache: http://foundation-registry-cache:5000 ``` ### 3.4 Named volumes (the stateful core — back these up, CONTRACT_004) | Volume | Mounted by | Holds | Backup? | |--------|-----------|-------|---------| | `foundation-forgejo-data` | forgejo | **git repos** (POSIX FS — irreducible), app.ini, host SSH keys | **Yes — critical** | | `foundation-postgres-data` | postgres | relational data (users, orgs, CI, package metadata) | **Yes** (via pg_dump) | | `foundation-vault-data` | vault | raft storage | **Yes** (via raft snapshot) | | `foundation-rustfs-data` | rustfs | blobs: LFS, packages, Actions artifacts | **Yes** (bucket-level) | | `foundation-caddy-data` | caddy | ACME certs/account | recreatable (re-issue) — optional | | `foundation-caddy-config` | caddy | autosave config | recreatable | Volume root maps under CONTRACT_001 `dataRoot` (e.g. `/srv/foundation/`). ## Ownership - `packages/pulumi-docker` provides the `DockerDeployments` primitive (name, image, ports, volumes, networks, envs) — vendored from olsicloud4 `modules/docker`. - Each service component owns exactly one container definition + its volumes; the **network is owned by `network.ts`** and created first. ## Assumptions - Single VM, single Docker daemon, RWO local volumes (no RWX — that's HA/Layer-1, PLAN-001 HA note). - Container restart policy `unless-stopped`; Vault re-seals on restart → unseal helper (ADR-004). ## Validation - After each component: `docker ps` shows the container healthy; an internal `curl`/`pg_isready` from a peer container resolves the name and connects. - Only ports 443/80/2222 are reachable from off-host (assert with an external probe). ## Change Process New service = add a row to §3.2 + §3.3, declare its volumes in §3.4, and (if external) justify the published port. Renaming a container is breaking (it is the DNS name) — version this contract.