2026-02-28 · 2 min read

One Port Is Enough

How collapsing every service down to a single published port and one Caddy instance ended the reverse-proxy sprawl in my homelab.

homelab · caddy · docker

For about two years my homelab had a reverse-proxy problem, which is to say it had four of them.

There was the nginx container in front of the media stack. There was a Traefik instance that came bundled with something I installed once and never fully removed. There was a second nginx doing TLS termination for exactly one app, because at the time that seemed easier than editing the first one. And there was Caddy, which I had added specifically to replace all of the above, and which had instead simply joined them.

Each one had its own certificate story. Two of them were renewing. I found out which two the way everyone finds out.

The rule that fixed it

The fix was not a tool. It was a rule:

An application publishes exactly one port and knows nothing about the edge.

That is it. No app container gets to terminate TLS. No app container gets to know its own public hostname. No app gets two published ports because its API and its frontend are "different." If a stack needs an API and a UI, the stack solves that internally and still shows the network one door.

What it looks like in practice

This site is the clearest example. It is a React frontend and an Express API with a MongoDB behind it — classically three things to route. It publishes one port:

services:
  server:
    ports:
      - "8472:5000"
  mongo:
    expose:
      - "27017"

Express serves the API under /api and the built SPA everywhere else, so the path split happens inside the process instead of inside the proxy. Mongo is never published at all; it is reachable only on the compose network.

And the edge config for the entire site is three lines:

robotrights.com {
    reverse_proxy localhost:8472
}

What I gave up

Honesty demands the trade-offs:

  • The frontend and API deploy together. They are one image. I cannot roll back the UI without rolling back the API. For a portfolio this is a feature; for a team of six it might not be.
  • No independent scaling. If the API needed ten replicas and the static assets needed one, this would be the wrong shape.
  • The build got slower. Multi-stage means the client builds on every server change unless I am careful with layer caching.

I would make the same trade again. The reverse-proxy sprawl was not costing me CPU, it was costing me the ability to remember how my own network worked at 11pm during an outage. One port is enough. One proxy is enough. The certificate that renews is the one you forgot you had, because it was never yours to manage.

All field notes