boma/roles/reverse_proxy/README.md
sjat 50b6445bdd feat(reverse_proxy): Caddy role (Gandi DNS-01, on-host image build, route catalog)
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>
2026-06-14 17:36:58 +02:00

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:

  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.