Table of Contents

Caddy Caddyfile Reload: Docker Bind Mount Inode Bug

When deploying Caddyfile changes to a running Caddy container, the standard rsync command will silently fail to update the container's view of the file. Caddy reload will report “config is unchanged” even though the file on disk is different.

Root Cause

rsync replaces files atomically by writing to a temp file and renaming it over the destination. The rename operation creates a new inode. Docker's single-file bind mount holds a reference to the original inode at mount time. After rsync replaces the file, the container's /etc/caddy/Caddyfile still points to the old inode, so Caddy never sees the new content.

This only affects single-file bind mounts (e.g. a specific file like Caddyfile). Directory bind mounts follow the path and do not have this problem.

Symptom

After running rsync to update the Caddyfile on the host and then running:

docker exec folkzone-caddy caddy reload --config /etc/caddy/Caddyfile

Caddy logs show:

"msg":"config is unchanged"

even though the host file has new content. Verifying with:

docker exec folkzone-caddy grep 'new-domain.folk.zone' /etc/caddy/Caddyfile

returns nothing, confirming the container sees the stale inode.

Fix

Use docker compose restart instead of caddy reload when deploying Caddyfile changes. Restarting the container remounts the bind mount from the current file path, picking up whichever inode is now at that path:

docker compose restart caddy

Do not use –force-recreate unless necessary, as that is slower and causes a longer downtime window. A plain restart is sufficient to refresh the bind mount.

Workaround (avoids restart)

Write to the file in-place on the host using shell redirection rather than rsync. This preserves the inode while updating the content:

cat /path/to/new/Caddyfile > /home/brennan/homelab/caddy/Caddyfile

Or use rsync with the –inplace flag, which writes directly to the existing file without creating a temp file:

rsync --inplace -avz -e "ssh -i ~/.omg-lol-keys/id_ed25519" \
  server/caddy/Caddyfile \
  brennan@192.168.1.65:/home/brennan/homelab/caddy/Caddyfile

After either of these, caddy reload will work correctly without a restart.

Correct Deployment Procedure

rsync --inplace -avz -e "ssh -i ~/.omg-lol-keys/id_ed25519" \
  server/caddy/Caddyfile \
  brennan@192.168.1.65:/home/brennan/homelab/caddy/Caddyfile
docker exec folkzone-caddy caddy reload --config /etc/caddy/Caddyfile

Or if you forget –inplace and already ran a plain rsync:

docker compose -f /home/brennan/homelab/docker-compose.yml restart caddy