ADR: Caddy serves the portals, replacing nginx #166

Closed
opened 2026-09-04 15:23:44 +00:00 by not · 0 comments
Contributor

Decision to record: serve the four portals with Caddy instead of nginx.

Context

Each portal image serves the built Angular app and reverse-proxies its own BFF endpoint
group, so the browser stays on one origin (no CORS, the token rides along — ADR-0010).
Two workarounds had accumulated around nginx's resolver, both for one root cause: nginx
resolves a variable proxy_pass upstream itself, using only the resolver directive and
never the search domains in /etc/resolv.conf.

  1. resolver 127.0.0.11 is Docker-specific, so apps/portal-nginx-resolver.sh rewrote it
    at container start for rootless podman (aardvark).
  2. On Kubernetes the bare bff name cannot resolve without the svc.cluster.local search
    domain, so that same script gained a BFF_HOST override set per portal by the Helm
    chart (#25).

Proposal

Replace the nginx runtime stage and the four nginx.conf files with caddy:2-alpine and a
Caddyfile per app. Caddy dials upstreams through the system resolver, which reads
/etc/resolv.conf — nameserver and search domains — so reverse_proxy bff:8080 resolves
under Docker, rootless podman and Kubernetes with no per-engine configuration, and still
starts before the BFF exists. Both workarounds and the chart's BFF_HOST env are deleted.

Routing uses mutually-exclusive handle blocks rather than a bare try_files: Caddy's
default directive order sorts rewrites before reverse_proxy, so a top-level SPA
fallback would rewrite every API path to /index.html before the proxy saw it.

Alternatives

  • Keep nginx (workaround stays, and it had already grown a second head for Kubernetes).
  • Keep nginx with a hard-coded FQDN (needs a different config per deployment target).
  • Drop the proxy and use CORS (inverts the same-origin design, ADR-0010).

Scope

apps/*/Dockerfile, apps/*/Caddyfile (new), apps/*/nginx.conf (deleted),
apps/portal-nginx-resolver.sh (deleted), the compose files' config.json mount paths,
the Helm chart, and the docs that name nginx. A contract test
(infra/test_portal_caddyfiles.py, in make unit) asserts each portal proxies exactly its
own endpoint groups and keeps the SPA fallback.

**Decision to record:** serve the four portals with Caddy instead of nginx. ## Context Each portal image serves the built Angular app and reverse-proxies *its own* BFF endpoint group, so the browser stays on one origin (no CORS, the token rides along — ADR-0010). Two workarounds had accumulated around nginx's resolver, both for one root cause: nginx resolves a variable `proxy_pass` upstream itself, using only the `resolver` directive and never the search domains in `/etc/resolv.conf`. 1. `resolver 127.0.0.11` is Docker-specific, so `apps/portal-nginx-resolver.sh` rewrote it at container start for rootless podman (aardvark). 2. On Kubernetes the bare `bff` name cannot resolve without the `svc.cluster.local` search domain, so that same script gained a `BFF_HOST` override set per portal by the Helm chart (#25). ## Proposal Replace the nginx runtime stage and the four `nginx.conf` files with `caddy:2-alpine` and a `Caddyfile` per app. Caddy dials upstreams through the system resolver, which reads `/etc/resolv.conf` — nameserver *and* search domains — so `reverse_proxy bff:8080` resolves under Docker, rootless podman and Kubernetes with no per-engine configuration, and still starts before the BFF exists. Both workarounds and the chart's `BFF_HOST` env are deleted. Routing uses mutually-exclusive `handle` blocks rather than a bare `try_files`: Caddy's default directive order sorts rewrites *before* `reverse_proxy`, so a top-level SPA fallback would rewrite every API path to `/index.html` before the proxy saw it. ## Alternatives - Keep nginx (workaround stays, and it had already grown a second head for Kubernetes). - Keep nginx with a hard-coded FQDN (needs a different config per deployment target). - Drop the proxy and use CORS (inverts the same-origin design, ADR-0010). ## Scope `apps/*/Dockerfile`, `apps/*/Caddyfile` (new), `apps/*/nginx.conf` (deleted), `apps/portal-nginx-resolver.sh` (deleted), the compose files' `config.json` mount paths, the Helm chart, and the docs that name nginx. A contract test (`infra/test_portal_caddyfiles.py`, in `make unit`) asserts each portal proxies exactly its own endpoint groups and keeps the SPA fallback.
not added the type:adr-proposalarea:portal-self-servicearea:portal-behandel labels 2026-09-04 15:23:59 +00:00
not closed this issue 2026-09-10 08:53:59 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: eho/register-referentie#166