networking
A tunnel that dials out, so there are no inbound ports
A tunnel that dials out, so there are no inbound ports
The normal shape of a self-hosted deployment is: the server listens on 80 and 443, a firewall allows those two, and everything else is closed. That's fine, and it still leaves two ports facing the entire internet, plus SSH if you're not careful, plus whatever a misconfigured container accidentally publishes to 0.0.0.0.
The setup I run instead: the production host publishes nothing to the host network. Every container talks to its neighbours on private Docker networks only. A tunnel client runs as one of those containers, makes a single outbound TLS connection to the edge provider, and all inbound traffic arrives back down that already-open connection. The firewall's inbound rules are, genuinely, "deny everything."
Why this is worth the dependency
- There is no port to scan. An attacker enumerating the host's IP finds nothing listening. The origin IP itself can stay effectively secret.
- A container that accidentally binds
0.0.0.0:9229— a debugger, a stray dev server — isn't suddenly exposed, because the host isn't reachable regardless of what a container publishes. - The edge absorbs volumetric attacks before they reach a connection the small VM has to service.
Run the tunnel client as two replicas on the same credentials — a single tunnel process is a single point of failure for the entire site, and a second one is nearly free.
What it costs
A hard dependency on one provider for reachability, and a config surface — the ingress rules mapping hostnames to internal services — that lives in their dashboard, not your repo. Keep a version-controlled mirror of that config even though it isn't the source of truth, so a lost dashboard is a reconstruction and not an outage you're debugging blind. And keep dev and production on separate tunnels — never reuse one set of credentials across environments.
The general principle: the most secure port is one that was never opened. If inbound reachability can be an outbound connection instead, that's a category of attack surface deleted rather than defended.