Skip to content

Add built-in HTTPS via an optional Caddy compose file - #15

Open
aderaaij wants to merge 1 commit into
mainfrom
feat/builtin-https
Open

aderaaij wants to merge 1 commit into
mainfrom
feat/builtin-https

Conversation

@aderaaij

Copy link
Copy Markdown
Owner

Making the server reachable from a phone was left to the reader: bring your own reverse proxy, or set up Tailscale on every device. For a server with a public IP and a domain it is now two lines in .env:

COMPOSE_FILE=docker-compose.yml:deploy/https/caddy.yml
DOMAIN=loopback.example.com

What changes

  • deploy/https/caddy.yml. An add-on compose file, same pattern as deploy/demo/docker-compose.public.yml. It runs Caddy, which obtains and renews a Let's Encrypt certificate and proxies to the app, and binds plain-HTTP port 8001 to the host's loopback.
  • Real client addresses. Behind any proxy, every client looked like the proxy: the login rate limit was one bucket shared by everyone, and the audit trail recorded the proxy's address on every event. The Caddy file sets FORWARDED_ALLOW_IPS, and the variable now passes through from .env for installs behind their own proxy. No application code changed; uvicorn does the rewriting.
  • Docs. "Exposing the server" in the README leads with the built-in option and states what Cloudflare Tunnel can see that Caddy and Tailscale cannot.

A default install is unchanged: nothing here is loaded unless COMPOSE_FILE names it.

Worth reviewing

  • FORWARDED_ALLOW_IPS=* in the Caddy file. It trusts the header from any peer, which is sound only because the same file makes port 8001 loopback-only. The two settings have to stay together; the file says so.
  • Add-on file, not a compose profile. A profile cannot change the app service's ports, so it could not close 8001.
  • Caddy does not share the app's network namespace. That would need no trust configuration, but I tested it: when the app container restarts on its own, the sidecar is stranded and the site stays down until it is restarted by hand.

Checked

Against the demo stack with DOMAIN=localhost (Caddy's internal certificate) on remapped ports:

  • Dashboard, API and /mcp all answer over HTTPS; HTTP redirects to HTTPS.
  • The audit trail records the connecting client's address, not Caddy's. A forged X-Forwarded-For sent through Caddy is ignored.
  • Five login attempts from one client hit the limit while a second client still gets through.
  • HTTPS keeps working after docker restart of the app, a kill and start, recreating only the app, and restarting Caddy.

Not checked: issuing a real Let's Encrypt certificate, which needs a public host. That part is stock Caddy behaviour.

Not in this PR

A Tailscale Funnel add-on for home servers without a domain. It is drafted, but it needs a tailnet auth key to test.

Making the server reachable from a phone was left to the reader: bring
your own reverse proxy, or set up Tailscale on every device. For a
server with a public IP and a domain it is now two lines in .env:

  COMPOSE_FILE=docker-compose.yml:deploy/https/caddy.yml
  DOMAIN=loopback.example.com

The add-on file runs Caddy, which obtains and renews a Let's Encrypt
certificate and proxies to the app. It also binds plain-HTTP port 8001
to the host's loopback, and sets FORWARDED_ALLOW_IPS so the server takes
the client address from X-Forwarded-For.

That last part fixes something any proxy setup had: every client looked
like the proxy, so the login rate limit was one bucket shared by
everyone (one client hammering the login locked everybody out) and the
audit trail recorded the proxy's address on every event.
FORWARDED_ALLOW_IPS now also passes through from .env for installs
behind their own proxy.

Caddy deliberately does not share the app's network namespace. That
would make the API see a loopback peer with no trust configuration, but
when the app container restarts on its own the sidecar is stranded in a
dead namespace and the site stays down until it is restarted by hand.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant