Give each app its own docker network, with caddy as the only hub #38

Open
opened 2026-10-15 20:58:55 +00:00 by hutao · 0 comments
Owner

Every web-facing container shares one docker network, proxy (infra.proxyNetwork): forgejo, dozzle, cloudflared, caddy, kuma, mailserver, searxng, navidrome, webmail and pages-hook. Docker lets every member reach every other member's ports by name, so a compromised app is one hop from all the others' internal listeners.

Why it matters

Several controls in this repo exist only at caddy:

  • the tailnet-only admin paths (blockedPaths): forgejo's /admin and /api/v1/admin/*, kuma's /socket.io/
  • every rate limit, including the login zones
  • basic_auth in front of searxng

A container on proxy talks to forgejo:3000, kuma:3001 and the rest directly, so none of those apply to it; each app's own login is all that remains. The ten members include five that face the internet. Splitting the box across machines would contain this too, but costs far more than splitting the network.

Proposal

One network per app, with caddy joined to all of them as the only hub. In modules/containers/default.nix, networks becomes one entry per app (net-forgejo, net-navidrome, …), and the per-container ordering is already derived from that attrset, so a container joining two networks keeps its dependency on both units.

The edges that are not caddy -> app, and have to survive:

  • webmail -> mailserver: ManageSieve at mailserver:4190 (mailserver.nix). IMAP and SMTP go to the public name and do not depend on the network. Put both on one net-mail.
  • forgejo -> pages-hook: the system webhook, allow-listed by container name (FORGEJO__webhook__ALLOWED_HOST_LIST). pages-hook joins net-forgejo.
  • cloudflared routes nothing today, so it needs no app network, only egress.
  • botnet stays as it is.

Check before and after

  • docker network inspect per network: caddy plus the app (plus the pairs above), nothing else.
  • Every public site and every tailnet vhost still answers; webmail's filter settings (ManageSieve) still load; a forgejo Actions run still triggers pages-pull.
  • From inside one app, another app's name no longer resolves.

Related, separate: the input chain's iifname "br-*" rule admits every non-bot container to the host's 3000/8384/8443. Once caddy has its own fixed address, that rule can match caddy alone.

Every web-facing container shares one docker network, `proxy` (`infra.proxyNetwork`): forgejo, dozzle, cloudflared, caddy, kuma, mailserver, searxng, navidrome, webmail and pages-hook. Docker lets every member reach every other member's ports by name, so a compromised app is one hop from all the others' internal listeners. ## Why it matters Several controls in this repo exist only at caddy: - the tailnet-only admin paths (`blockedPaths`): forgejo's `/admin` and `/api/v1/admin/*`, kuma's `/socket.io/` - every rate limit, including the login zones - `basic_auth` in front of searxng A container on `proxy` talks to `forgejo:3000`, `kuma:3001` and the rest directly, so none of those apply to it; each app's own login is all that remains. The ten members include five that face the internet. Splitting the box across machines would contain this too, but costs far more than splitting the network. ## Proposal One network per app, with caddy joined to all of them as the only hub. In `modules/containers/default.nix`, `networks` becomes one entry per app (`net-forgejo`, `net-navidrome`, …), and the per-container ordering is already derived from that attrset, so a container joining two networks keeps its dependency on both units. The edges that are not caddy -> app, and have to survive: - **webmail -> mailserver**: ManageSieve at `mailserver:4190` (`mailserver.nix`). IMAP and SMTP go to the public name and do not depend on the network. Put both on one `net-mail`. - **forgejo -> pages-hook**: the system webhook, allow-listed by container name (`FORGEJO__webhook__ALLOWED_HOST_LIST`). pages-hook joins `net-forgejo`. - **cloudflared** routes nothing today, so it needs no app network, only egress. - **botnet** stays as it is. ## Check before and after - `docker network inspect` per network: caddy plus the app (plus the pairs above), nothing else. - Every public site and every tailnet vhost still answers; webmail's filter settings (ManageSieve) still load; a forgejo Actions run still triggers pages-pull. - From inside one app, another app's name no longer resolves. Related, separate: the input chain's `iifname "br-*"` rule admits every non-bot container to the host's 3000/8384/8443. Once caddy has its own fixed address, that rule can match caddy alone.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
hutao/vps#38
No description provided.