vps cluster docs

0009. The VCAP Postgres instances stay publicly reachable, on moved non-default ports

Superseded further on 2026-10-27. The v1 edge on 15434 is also removed. The vcap v1 platform was retired and the firewall rule for 15434 is gone. No vcap Postgres port is open. See ../runbooks/vcap-v1-postgres-access.md.

Status: SUPERSEDED on 2026-10-23. The v2 vcap-dev and vcap-staging workloads were retired. Ports 15432 and 15433 are closed, their Traefik entrypoints and routes are removed, and their firewall rules are removed. At that date only the v1 edge on 15434 remained. See ../runbooks/vcap-postgres-access.md and ../../changelog/2026-10.md. The text below records the earlier decision. It is not current guidance.

Context

The requirement this replaces was absolute: no public TCP exposure of databases (SR-4). The architecture review’s S6 stage said the same thing concretely — close 5432/5433 to the world and keep the databases reachable only from inside.

What that collides with: the tenant’s developers are external. They are not in the tailnet, they own their own client tooling, and they connect to the dev and staging databases directly. Every method of removing the public port therefore asks a third party to change how they work — and the compensating hardening that would make an exposed port less dangerous (TLS, per-person roles) is tenant-chart work that cannot be written before the tenant agreement exists.

The live state, measured 2026-09-29 rather than inherited from the plan:

Decision

Keep both instances publicly reachable, move them off the default ports (dev 15432, staging 15433), and compensate at the application layer rather than at the network edge:

SR-4 is relaxed to match: instead of “no public TCP exposure of databases” it requires “no database on a default port, TLS, per-person non-superuser roles, and the superuser confined to the pod network”. The residual risk is the owner’s, accepted explicitly, and recorded in the plan rather than softened.

Alternatives rejected

Consequences

The risk, stated as a risk: a public Postgres port serving class-A student data, continuously scanned and credential-stuffed. Two consequences follow that are easy to miss:

The public repository must not advertise a property the cluster does not have. docs/ports.md keeps “what is true today” and “what is aimed at” in separate tables for this reason, and terraform/firewall.tf (now in the cluster checkout, cluster/terraform/firewall.tf) carries the same split in its own words.

Evidence