# Caddyfile for the Familienarchiv host. # # Caddy runs on the host (not in a container) and reverse-proxies into # the docker compose stacks bound to 127.0.0.1. # # Naming convention for ports (also documented in docker-compose.prod.yml): # production: backend 8080, frontend 3000 # staging: backend 8081, frontend 3001 # gitea: 3005 # # Security headers and the /actuator block apply to both archive vhosts. # X-Frame-Options is deliberately NOT set here: Spring Security configures # frame-options SAMEORIGIN (for the in-app PDF preview iframe). Setting # DENY in Caddy would conflict. (security_headers) { header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" Referrer-Policy "strict-origin-when-cross-origin" # Deny browser APIs the app does not use. Reduces blast radius of an # XSS landing in a privileged origin: a payload cannot silently turn # on the microphone or read geolocation. Permissions-Policy "camera=(), microphone=(), geolocation=()" -Server } } (block_actuator) { # Defense in depth: even if management.endpoints.web.exposure.include grows # in application.yaml, /actuator/* is unreachable externally. The internal # Prometheus scrape (future) talks to the backend directly on the docker # network, not via Caddy. # # Why a `handle` block and not a top-level `respond @matcher`: each archive # vhost has a catch-all `handle { reverse_proxy ... }` that matches every # path including /actuator/*, and Caddy's `handle` blocks are mutually # exclusive. Without our own `handle /actuator/*` the catch-all wins, the # request is proxied to the backend, and Spring Security 302s to /login # instead of Caddy returning 404. See #512. handle /actuator/* { respond 404 } } (access_log) { # JSON access log for fail2ban. The jail at infra/fail2ban/familienarchiv.conf # watches this file for 401 responses on /api/auth/login. # Caddy auto-creates /var/log/caddy/ when running as the `caddy` system user. log { output file /var/log/caddy/access.log { roll_size 10mb roll_keep 14 } format json } } archiv.raddatz.cloud { import security_headers import block_actuator import access_log handle /api/* { reverse_proxy 127.0.0.1:8080 } handle { reverse_proxy 127.0.0.1:3000 } } staging.raddatz.cloud { import security_headers import block_actuator import access_log handle /api/* { reverse_proxy 127.0.0.1:8081 } handle { reverse_proxy 127.0.0.1:3001 } } git.raddatz.cloud { import security_headers reverse_proxy 127.0.0.1:3005 }