This one is usable today — the port registry infrastructure it depends on
(inventory/ports.yaml, scripts/check-ports.py, docs/ports.md) already exists (S1).
Check the registry for collisions: ../ports.md (human-readable) or
../../inventory/ports.yaml (source of truth).
ssh vps "ss -Hltnp; ss -Hlunp"
kubectl get svc -A -o wide | grep -E 'NodePort|LoadBalancer'
Check the firewall. A Hetzner Cloud Firewall does exist now (S2), but it is deliberately
permissive — it allows 22, 80, 443, 6443, 5432, 5433, udp/41641 and icmp from anywhere, and
nothing else. So a new port is not automatically reachable from the internet, but neither is it
protected by policy: check terraform/firewall.tf in the cluster checkout
(cluster/terraform/firewall.tf) and decide whether the new port needs a rule
(permanent) or a break-glass one. Anything you expose via NodePort/LoadBalancer/hostPort
without a matching firewall rule will be reachable from inside the cluster and the tailnet, and
blocked from the internet — which is usually what you want, but state it explicitly in the
registry entry’s firewall_rule field.
Add an entry to inventory/ports.yaml with every required field: port, proto,
service, k8s_service, namespace, owner, scope (internal/tailnet/public),
status (live/target/remove), reason, firewall_rule, ingress, auth, temporary,
expires.
python3 scripts/check-ports.py
Fails if your new entry duplicates an existing (port, proto, scope) pair with status: live
or target, or if a NodePort/LoadBalancer Service/hostPort in k8s/ has no matching
registry entry at all. Fix any reported collision before continuing.
Add the actual manifest (Service, Ingress, or container hostPort) under k8s/apps/<app>/
following the conventions in ../../AGENTS.md.
Update ../ports.md’s table by hand to match the new YAML entry (no
generator script yet — keep them in sync manually).
docs/ports.md — with a
changelog entry per ../../.claude/skills/changelog/SKILL.md.
Push to main.../ports.md / ../../inventory/ports.yaml../networking.md — firewall layers this port will sit behind