4.5 KiB
Docker Volume Migration Pitfalls
Common failures when moving Docker services between hosts.
Permission denied on data volume (n8n, Jul 10 2026)
Symptom: Container starts but immediately crashes with EACCES: permission denied, open '/home/node/.n8n/crash.journal'
Root cause: Docker named volumes copied from host A (scp the _data directory) carry the UID/GID from host A's filesystem. The container on host B runs as a different UID (n8n uses UID 1000, node user). The files are owned by root or ippadmin from the copy operation, not UID 1000.
Fix:
VOL=$(docker volume inspect <volume_name> --format "{{.Mountpoint}}")
chown -R 1000:1000 "$VOL"
docker compose restart <service>
For n8n specifically: UID 1000 is the node user inside the container. Both n8n_data and postgres_data volumes may need this fix.
Prevention: After copying volume data to a new host, always check ownership before starting containers:
ls -la /var/lib/docker/volumes/<volume>/_data/ | head -5
Volume data owned by wrong user (general)
Any Docker container that writes to a bind-mounted or named volume will have files owned by the container's internal UID. When copying between hosts:
- Identify the container's UID:
docker run --rm <image> id - Apply:
chown -R <UID>:<UID> <volume_path> - Then start the container
SSL redirect loop after migration (n8n, Jul 10 2026)
Symptom: n8n returns 308 redirect loop when accessed through Caddy with HTTPS
Root cause: n8n's N8N_PROTOCOL is set to https but Caddy terminates SSL. n8n sees the request came via HTTP (from Caddy's proxy) and redirects to HTTPS, creating an infinite loop.
Fix: Set N8N_PROTOCOL=http in .env — Caddy handles SSL termination, n8n should trust the proxy.
Caddy handle fallback overrides path routes (Jul 10, 2026)
Symptom: Browser shows "app1 — /n8n/ /webui/ /litellm/" instead of the app.
Root cause: A handle { respond "..." 200 } at the bottom of the Caddyfile catches ALL requests — including /n8n/signin — because Caddy evaluates handle blocks before handle_path directives.
Fix: Replace the handle { respond ... } with a bare reverse_proxy as the catch-all. Path-specific handle_path routes take priority over the catch-all reverse_proxy, but NOT over a catch-all handle block.
Lesson: Only use handle blocks for specific path prefixes. For a true catch-all, use reverse_proxy directly without wrapping in handle. Never put handle { respond } as a fallback when you also have handle_path routes — it overrides them.
Symptom: app1.itpropartner.com/n8n/ shows the app but all assets (JS, CSS, images) 404
Root cause: handle_path /n8n/* correctly strips the prefix before proxying, so n8n sees /signin instead of /n8n/signin. But n8n's HTML references assets at /assets/..., /static/... — these paths DON'T match /n8n/* and fall through to the default handler.
Fix: Give the service its own subdomain instead of a sub-path. Path-based routing only works for apps that support a baseUrl config option. n8n does not.
Lesson: Use sub-domain routing (n8n.itpropartner.com) for apps without baseUrl support, sub-path routing (domain.com/app/) only for apps that support it.
Let's Encrypt validation blocked by UFW / DNS (Jul 10, 2026)
Symptom: Caddy log shows authorization failed: HTTP 400 ... Timeout during connect (likely firewall problem). Cert never issues.
Root cause: UFW default deny blocks HTTP-01 challenge from Let's Encrypt's servers, OR DNS still points to wrong IP (stale SiteGround records during Cloudflare migration).
Fix: (1) Ensure ufw allow 80/tcp and ufw allow 443/tcp on the target server. (2) Verify DNS resolves to the correct IP from outside the server: ping app1.itpropartner.com. Core's DNS may be stale while your laptop's is fresh — test from an external source.
n8n Caddy deployment checklist (Jul 10, 2026)
When deploying n8n behind Caddy on a new host:
N8N_PROTOCOL=http— Caddy terminates SSLN8N_HOST=<domain>— match the public domainWEBHOOK_URL=https://<domain>/— for webhook callbacks- Caddy:
reverse_proxy 127.0.0.1:5678 { flush_interval -1 }— WebSocket support - Caddy:
header { X-Forwarded-Proto https; X-Forwarded-Host {host} }— proxy headers - UFW: allow 80/tcp + 443/tcp for Let's Encrypt validation
- DNS must resolve to this server's IP BEFORE starting Caddy (otherwise cert issuance blocks on first request)