- new compose templates + READMEs for the three LXC apps (110-112) - Makefile deploy targets (rackula pre-owns its data dir for uid 1001) - Caddy vhosts for all four, incl. Hermes /auth/login redirect workaround - firewall docs: CADDY->TRUSTED rules for LXCs 110-113 |
||
|---|---|---|
| .. | ||
| .env.sample | ||
| Caddyfile_template | ||
| Dockerfile | ||
| README.md | ||
| template.yaml | ||
Caddy
Docker deployment for Caddy with the Cloudflare DNS module. Run all commands from the repository root.
Persistent Data
Caddy stores certificates, ACME state, and autosaved configuration in the
Docker-managed named volumes caddy_data and caddy_config. Named volumes
remain writable when Docker uses rootless or user-namespace UID mapping.
Installation
-
Create
.envif it does not exist:cp -n .env.sample .env -
Set the required values in
.env:TZ=Africa/Johannesburg CADDY_TLS_EMAIL=admin@example.com CADDY_BASE_DOMAIN=home.example.com CLOUDFLARE_DNS_TOKEN=<restricted-cloudflare-api-token> CADDY_LAN_PREFIX=10.0.1 CADDY_OFFICE_PREFIX=10.0.10 CADDY_IOT_PREFIX=10.0.30 DNS_BIND_IP=10.0.10.5 CADDY_MEM_LIMIT=256m DOCKERLOGGING_MAXFILE=3 DOCKERLOGGING_MAXSIZE=10mThe Cloudflare token needs zone read and DNS edit permissions.
-
Render the templates:
./substitute_env.sh docker-compose-files/caddy/Caddyfile_template docker-compose-files/caddy/Caddyfile .env ./substitute_env.sh docker-compose-files/caddy/template.yaml docker-compose.caddy.yml .env chmod 644 docker-compose-files/caddy/CaddyfileEdit
Caddyfile_template, not the generatedCaddyfile.docker-compose.caddy.ymlcontains the rendered token and is ignored by Git. Keep that generated Compose file private. The generatedCaddyfiledoes not contain the Cloudflare token; it must be readable by Caddy through the read-only bind mount. -
Validate and start Caddy:
docker compose -f docker-compose.caddy.yml config --quiet docker compose -f docker-compose.caddy.yml build docker compose -f docker-compose.caddy.yml run --rm caddy caddy validate --config /etc/caddy/Caddyfile docker compose -f docker-compose.caddy.yml up -d -
Verify the container:
docker compose -f docker-compose.caddy.yml ps docker compose -f docker-compose.caddy.yml logs --tail=100 caddy
Apply Configuration Changes
After changing .env or template.yaml, render the Compose file and recreate
the container:
./substitute_env.sh docker-compose-files/caddy/template.yaml docker-compose.caddy.yml .env
docker compose -f docker-compose.caddy.yml config --quiet
docker compose -f docker-compose.caddy.yml up -d --build
After changing Caddyfile_template, render, validate, and reload it:
./substitute_env.sh docker-compose-files/caddy/Caddyfile_template docker-compose-files/caddy/Caddyfile .env
chmod 644 docker-compose-files/caddy/Caddyfile
docker compose -f docker-compose.caddy.yml exec caddy caddy validate --config /etc/caddy/Caddyfile
docker compose -f docker-compose.caddy.yml exec caddy caddy reload --config /etc/caddy/Caddyfile
Initial Setup
In your internal DNS server:
- Add an
Arecord namedcaddypointing to the Caddy host at10.0.15.5. - Add a wildcard
CNAMEnamed*pointing tocaddy.home.example.com.
Replace home.example.com with CADDY_BASE_DOMAIN.
Network Requirements
- Caddy uses host networking and listens on ports
80and443. - Allow Caddy to reach every upstream IP and port in
Caddyfile_template. - Allow outbound DNS and HTTPS for Cloudflare DNS-01 certificate issuance.
- Exclude the Caddy host from DNS redirection so it can query public resolvers.
- The admin API listens only on
localhost:2019.
Client VLAN Access
For client VLANs to reach Caddy over HTTPS, add a firewall allow rule for each VLAN that needs access. In UniFi Settings > Policy Engine > Policy Table:
| Name | Source zone | Destination zone | Destination IP | Protocol | Destination port |
|---|---|---|---|---|---|
Allow FAMILY to Caddy |
FAMILY |
DMZ |
10.0.15.5 |
TCP | 443 |
Allow MEDIA to Caddy |
MEDIA |
DMZ |
10.0.15.5 |
TCP | 443 |
Add further rows for any other zone that needs access to internal services via
Caddy. No Caddyfile changes are required — the wildcard DNS record in Technitium
(*.home.example.com → caddy.home.example.com → 10.0.15.5) handles
resolution automatically for all VLANs that use Technitium as their DNS server.
Troubleshooting
Validate the generated Caddyfile and inspect container logs:
chmod 644 docker-compose-files/caddy/Caddyfile
docker compose -f docker-compose.caddy.yml run --rm caddy caddy validate --config /etc/caddy/Caddyfile
docker compose -f docker-compose.caddy.yml logs --tail=100 caddy
If validation reports open /etc/caddy/Caddyfile: permission denied, the
generated host file is not readable by the container's mapped user. Confirm
its mode with ls -l docker-compose-files/caddy/Caddyfile, then apply the
chmod 644 command above. Do not make docker-compose.caddy.yml
world-readable because it contains the rendered Cloudflare token.
If logs report mkdir /data/caddy: permission denied or
mkdir /config/caddy: permission denied, re-render docker-compose.caddy.yml
from the current template and recreate Caddy. The current template uses
Docker-managed named volumes so rootless/user-namespace UID mapping does not
prevent writes:
./substitute_env.sh docker-compose-files/caddy/template.yaml docker-compose.caddy.yml .env
docker compose -f docker-compose.caddy.yml down
docker compose -f docker-compose.caddy.yml up -d --build
docker compose -f docker-compose.caddy.yml logs --tail=100 caddy
If certificate issuance fails, confirm the Cloudflare token permissions and ensure Caddy's public DNS queries are not redirected to internal DNS.
Raspberry Pi OS Memory Limits
If Docker reports the following warning, Caddy still starts, but Docker ignores
CADDY_MEM_LIMIT:
Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.
Enable memory cgroups by appending these parameters to the existing single line
in /boot/firmware/cmdline.txt:
cgroup_enable=memory cgroup_memory=1
Older Raspberry Pi OS releases may use /boot/cmdline.txt. Do not add a new
line to either file. Reboot and verify:
sudo reboot
docker info | grep -i warning
docker stats --no-stream caddy
Useful Commands
# Confirm the Cloudflare module is installed
docker compose -f docker-compose.caddy.yml exec caddy caddy list-modules | grep dns.providers.cloudflare
# Rebuild after image or Dockerfile updates
docker compose -f docker-compose.caddy.yml build --pull --no-cache
docker compose -f docker-compose.caddy.yml up -d
Backup
Back up both named volumes. The data volume contains certificates and ACME state:
mkdir -p backups/caddy
docker run --rm \
-v homelab-blueprint_caddy_data:/source:ro \
-v "$PWD/backups/caddy:/backup" \
alpine:3.20 tar -czf /backup/caddy-data.tar.gz -C /source .
docker run --rm \
-v homelab-blueprint_caddy_config:/source:ro \
-v "$PWD/backups/caddy:/backup" \
alpine:3.20 tar -czf /backup/caddy-config.tar.gz -C /source .
Confirm the volume keys with
docker compose -f docker-compose.caddy.yml config --volumes and the actual
Docker volume names with docker volume ls before backup if the Compose
project name differs from homelab-blueprint.