Implements the Caddy reverse proxy role (ADR-024): builds boma/caddy-gandi:latest on-host (caddy-dns/gandi plugin), renders Caddyfile from route catalog, brings Compose project up. Adds community.docker to requirements.yml, production group_vars, and a caddy-image Makefile target. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.1 KiB
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:
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:
caddy:2-builder— usesxcaddyto compile Caddy withcaddy-dns/gandi.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.