From d407aeabb24b1362c52a40b69dc9b549273de8ce Mon Sep 17 00:00:00 2001 From: sjat Date: Mon, 15 Jun 2026 06:57:38 +0200 Subject: [PATCH] feat(docker): custom Caddy image with the Gandi DNS-01 plugin Compiles caddy-dns/gandi v1.1.0 into Caddy v2.11.4 via xcaddy so mesh/LAN-only hosts (no public A-record) can issue certs via ACME DNS-01. Pinned per ADR-011/014. The M4a attempt failed for two reasons, both addressed here: - built on a Hetzner IP -> Google's Go module proxy 403s those ranges. The Makefile target is documented to build on ubongo, then push to Forgejo. - older libdns/gandi sent Gandi's deprecated Apikey header. v1.1.0 sends the PAT as Authorization: Bearer to api.gandi.net/v5/livedns. make caddy-image / caddy-image-push mirror the molecule-image targets. Co-Authored-By: Claude Opus 4.8 (1M context) --- .docker/caddy-gandi/Dockerfile | 22 ++++++++++++++++++++++ Makefile | 18 +++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .docker/caddy-gandi/Dockerfile diff --git a/.docker/caddy-gandi/Dockerfile b/.docker/caddy-gandi/Dockerfile new file mode 100644 index 0000000..f34dc42 --- /dev/null +++ b/.docker/caddy-gandi/Dockerfile @@ -0,0 +1,22 @@ +# syntax=docker/dockerfile:1 +# Custom Caddy image: vanilla Caddy + the Gandi DNS-01 plugin (ADR-024). +# +# WHY: mesh/LAN-only services have no public A-record, so they cannot satisfy ACME +# HTTP-01; they need DNS-01 against Gandi (the M1 *. wildcard strategy). +# Caddy's official image ships no third-party DNS plugins, so we compile one in. +# +# WHERE to build: on ubongo (the control node) — NOT on askari/Hetzner. Google's Go +# module proxy 403s Hetzner IP ranges, which broke the original on-host build (M4a). +# Build here, push the pinned tag/digest to the Forgejo registry, pull on askari. +# +# Versions pinned (ADR-011/ADR-014). caddy-dns/gandi v1.1.0 -> libdns/gandi v1.1.0, +# which authenticates with a Gandi Personal Access Token via "Authorization: Bearer" +# against https://api.gandi.net/v5/livedns (the legacy Apikey scheme is gone — using +# a PAT in the old Apikey slot 403s, which is what sank the M4a attempt). +# verified: caddy-dns/gandi v1.1.0 sends the PAT as Bearer · WebFetch libdns/gandi +# client.go @master (go.mod requires v1.1.0) · 2026-06-15 +FROM caddy:2.11.4-builder AS build +RUN xcaddy build v2.11.4 --with github.com/caddy-dns/gandi@v1.1.0 + +FROM caddy:2.11.4 +COPY --from=build /usr/bin/caddy /usr/bin/caddy diff --git a/Makefile b/Makefile index c3bdcb6..77193b4 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,10 @@ TF := terraform TF_ENV ?= staging MOLECULE_IMAGE := forgejo.nyumbani.baobab.band/sjat/molecule-debian13:latest MOLECULE_DOCKERFILE := .docker/molecule-debian13/Dockerfile +# Custom Caddy + Gandi DNS-01 plugin (ADR-024). Build on ubongo, NOT askari/Hetzner +# (the Go module proxy 403s Hetzner IPs); push the pinned tag to the Forgejo registry. +CADDY_IMAGE := forgejo.nyumbani.baobab.band/sjat/caddy-gandi:2.11.4 +CADDY_DOCKERFILE := .docker/caddy-gandi/Dockerfile # For TF_ENV=offsite, source the Hetzner token from the vault into the environment # (rbw must be unlocked). Read in-memory; never written to a tfvars file (CLAUDE.md). @@ -33,7 +37,7 @@ endif .PHONY: help setup collections lint test test-all check deploy encrypt decrypt \ edit-vault check-vault new-role \ tf-init tf-plan tf-apply tf-output tf-inventory tf-inventory-offsite \ - molecule-image molecule-image-push + molecule-image molecule-image-push caddy-image caddy-image-push help: @echo "" @@ -63,6 +67,8 @@ help: @echo "" @echo " make molecule-image Build the Molecule test image locally" @echo " make molecule-image-push Push the test image to the Forgejo registry" + @echo " make caddy-image Build the custom Caddy + Gandi DNS-01 image (run on ubongo)" + @echo " make caddy-image-push Push the Caddy image to the Forgejo registry" @echo "" # ── Environment setup ───────────────────────────────────────────────────────── @@ -143,6 +149,16 @@ molecule-image: molecule-image-push: molecule-image docker push $(MOLECULE_IMAGE) +# ── Custom Caddy image (Gandi DNS-01 plugin, ADR-024) ───────────────────────── +# DNS-01 (wildcard / mesh-LAN-only certs) needs the caddy-dns/gandi plugin compiled +# in via xcaddy. Build on ubongo — Google's Go module proxy 403s Hetzner IPs. + +caddy-image: + docker build -t $(CADDY_IMAGE) -f $(CADDY_DOCKERFILE) .docker/caddy-gandi + +caddy-image-push: caddy-image + docker push $(CADDY_IMAGE) + # ── Terraform ───────────────────────────────────────────────────────────────── tf-init: