This guide covers operational practices for running caddy-config in production environments.
compose.yaml StackCreate a .env file alongside compose.yaml to provide the trust token (no --os-env needed):
echo "INCUS_TOKEN=$(incus config trust add --quiet caddy-config)" > .env
Here is the recommended production stack deploying Caddy alongside caddy-config:
services:
caddy:
image: docker.io/library/caddy:2.11.4-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- caddy-config:/config
- caddy-data:/data
command: >-
sh -c '
if [ ! -f /config/Caddyfile ]; then
echo "{\n\tadmin localhost:2019\n}\n:80 {\n\trespond \"Initializing edge proxy...\" 503\n}\n" > /config/Caddyfile;
fi;
exec caddy run --config /config/Caddyfile --adapter caddyfile'
caddy-config:
image: ghcr.io/lxc/incus-caddy-config:latest
restart: unless-stopped
ports:
- "9153:9153"
environment:
INCUS_CADDY_INCUS: https://10.0.1.1:8443
INCUS_CADDY_DATA_DIR: /var/lib/caddy-config
INCUS_CADDY_INSTANCES: "edge:default:caddy"
INCUS_CADDY_HTTP_ADDRESS: ":9153"
INCUS_CADDY_LOG: "INFO"
secrets:
- token
volumes:
- config-data:/var/lib/caddy-config
depends_on:
caddy:
condition: service_started
secrets:
token:
environment: INCUS_TOKEN
volumes:
caddy-config:
caddy-data:
config-data:
caddy-config:/config:
Persistent Incus custom storage volume holding /config/Caddyfile. caddy-config connects directly to this volume over SFTP to write and update the configuration.caddy-data:/data:
Persistent volume where Caddy stores automatic TLS certificates (from Let's Encrypt / ZeroSSL) and OCSP stapling cache.config-data:/var/lib/caddy-config:
Stores caddy-config's enrolled client TLS certificate (client.crt, client.key) so trust tokens are only needed once.Because /config is backed by a custom storage volume:
incus-compose restart caddy) or the Incus host reboots, Caddy immediately starts up using the persisted Caddyfile on the volume.caddy-config to initialize.If Caddy is temporarily stopped (e.g. during maintenance or image upgrades):
caddy-config detects the container is stopped./config and writes the updated Caddyfile directly to the volume via SFTP.By default, caddy-config monitors all visible Incus projects. You can restrict monitoring to specific projects:
caddy-config run \
--project prod \
--project staging \
--caddy-instance edge:default:caddy
You can route different services to different Caddy instances using separate label prefixes:
caddy-config run \
--caddy-instance public:default:caddy-external \
--caddy-instance internal:default:caddy-internal
user.label.public.domain route to caddy-external.user.label.internal.domain route to caddy-internal.Inspect the active configuration directly inside the Caddy container:
incus-compose exec caddy cat /config/Caddyfile
Increase log verbosity using INCUS_CADDY_LOG=DEBUG or TRACE:
incus-compose logs -f caddy-config
DEBUG logs display:
instance-started, instance-stopped, instance-renamed).caddy validate exit codes and output.If you suspect a configuration syntax issue:
incus-compose exec caddy caddy validate --config /config/Caddyfile --adapter caddyfile
opening SFTP session for volume ... not found:
Verify that Caddy's /config directory is backed by a named storage volume in compose.yaml.executing caddy reload: connection refused:
Verify that Caddy's global block includes { admin localhost:2019 }. If admin off was specified, Caddy cannot process reload signals over its internal Admin API.readiness 503 Service Unavailable:
Indicates the chain is still performing the initial fleet sweep or is disconnected from the Incus API. Check network connectivity to Incus.