211 lines
7.1 KiB
Markdown
211 lines
7.1 KiB
Markdown
# 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
|
|
|
|
1. Create `.env` if it does not exist:
|
|
|
|
```bash
|
|
cp -n .env.sample .env
|
|
```
|
|
|
|
2. Set the required values in `.env`:
|
|
|
|
```dotenv
|
|
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=10m
|
|
```
|
|
|
|
The Cloudflare token needs zone read and DNS edit permissions.
|
|
|
|
3. Render the templates:
|
|
|
|
```bash
|
|
./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/Caddyfile
|
|
```
|
|
|
|
Edit `Caddyfile_template`, not the generated `Caddyfile`.
|
|
`docker-compose.caddy.yml` contains the rendered token and is ignored by Git.
|
|
Keep that generated Compose file private. The generated `Caddyfile` does not
|
|
contain the Cloudflare token; it must be readable by Caddy through the
|
|
read-only bind mount.
|
|
|
|
4. Validate and start Caddy:
|
|
|
|
```bash
|
|
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
|
|
```
|
|
|
|
5. Verify the container:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
./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:
|
|
|
|
```bash
|
|
./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:
|
|
|
|
1. Add an `A` record named `caddy` pointing to the Caddy host at `10.0.15.5`.
|
|
2. Add a wildcard `CNAME` named `*` pointing to
|
|
`caddy.home.example.com`.
|
|
|
|
Replace `home.example.com` with `CADDY_BASE_DOMAIN`.
|
|
|
|
## Network Requirements
|
|
|
|
- Caddy uses host networking and listens on ports `80` and `443`.
|
|
- 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
./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`:
|
|
|
|
```text
|
|
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`:
|
|
|
|
```text
|
|
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:
|
|
|
|
```bash
|
|
sudo reboot
|
|
docker info | grep -i warning
|
|
docker stats --no-stream caddy
|
|
```
|
|
|
|
## Useful Commands
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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`.
|