# reverse_proxy Boma's standard Caddy reverse proxy (ADR-024). Runs on `askari` (the off-site Hetzner host) and terminates TLS for all public-facing services via ACME DNS-01 against Gandi LiveDNS. The custom Caddy image (with the `caddy-dns/gandi` plugin) is built directly on the host because `askari` cannot reach the internal registry before the mesh VPN is established. ## How TLS works Caddy obtains wildcard certificates for `*.{{ reverse_proxy__acme_domain }}` using the ACME DNS-01 challenge. The Gandi PAT (`vault.gandi.pat`) is injected into the container at runtime via an `.env` file as `GANDI_BEARER_TOKEN`. Caddy reads it with `{env.GANDI_BEARER_TOKEN}` in the Caddyfile so the secret never lands in a config file on disk. ## Route catalog — `reverse_proxy__routes` Services register themselves as routes by appending an entry to `reverse_proxy__routes` in `group_vars/all/reverse_proxy.yml`: ```yaml reverse_proxy__routes: - host: netbird.askari.wingu.me upstream: "netbird-management:443" - host: dashboard.askari.wingu.me upstream: "dashboard:8080" ``` Each entry renders a named matcher and a `handle` block in the Caddyfile: ``` @netbird_askari_wingu_me host netbird.askari.wingu.me handle @netbird_askari_wingu_me { reverse_proxy netbird-management:443 } ``` Requests that match no route receive a plain `200 "boma reverse proxy"` response. ## On-host image build The Dockerfile at `roles/reverse_proxy/files/Dockerfile` builds a two-stage image: 1. `caddy:2-builder` — uses `xcaddy` to compile Caddy with `caddy-dns/gandi`. 2. `caddy:2` — copies the compiled binary into the slim runtime image. The Ansible role copies the Dockerfile to `{{ reverse_proxy__base_dir }}` and calls `community.docker.docker_image` to build `boma/caddy-gandi:latest` on the host. The build is only re-triggered when the Dockerfile changes (`force_source: "{{ _caddy_dockerfile.changed }}"`). For local dev or CI: `make caddy-image`. ## Variables | Variable | Default | Description | |---|---|---| | `reverse_proxy__image` | `boma/caddy-gandi:latest` | Docker image name (built on-host) | | `reverse_proxy__base_dir` | `/opt/services/reverse_proxy` | Working directory for Compose project | | `reverse_proxy__acme_domain` | `example.test` | Wildcard domain for ACME cert | | `reverse_proxy__acme_email` | `admin@example.test` | ACME registration email | | `reverse_proxy__routes` | `[]` | List of `{host, upstream}` route entries | | `reverse_proxy__manage` | `true` | Set `false` in Molecule to skip Docker tasks | Production overrides live in `inventories/production/group_vars/all/reverse_proxy.yml`. ## `reverse_proxy__manage` toggle Docker operations (image build, `docker compose up`) are gated on `reverse_proxy__manage | bool`. Set it to `false` in Molecule so the role can be tested (template rendering, directory creation) without a Docker daemon. ## Secrets | Secret path | Usage | |---|---| | `vault.gandi.pat` | Gandi PAT injected as `GANDI_BEARER_TOKEN` | Stored encrypted in `inventories/production/group_vars/all/vault.yml`. Decrypt with `make decrypt FILE=...` before editing; re-encrypt after.