homelab-blueprint/docs/TROUBLESHOOTING.md
2026-06-15 20:07:48 +02:00

9.5 KiB

Docker Stack Troubleshooting Guide

Quick diagnostics

Run these first:

docker ps -a
docker stats --no-stream
docker network ls
docker-compose -f docker-compose.arr-stack.yml config

Common issues

1. Variables not substituted

Symptoms:

  • Generated file still contains {{VAR}}
  • Container fails due to missing env values

Fix:

./substitute_env.sh docker-compose-files/arr-stack/template.yaml docker-compose.arr-stack.yml

The script prints any unresolved placeholders. Add missing keys to .env (or provide defaults in templates).

2. Recyclarr errors (base_url must start with http)

Use service names, not localhost:

sonarr:
  sonarr-main:
    base_url: http://sonarr:8989

radarr:
  radarr-main:
    base_url: http://radarr:7878

Generate your file from this repo template:

./substitute_env.sh docker-compose-files/arr-stack/config/recyclarr_template.yml /opt/arr-stack/appdata/recyclarr/recyclarr.yml

2b. Recyclarr errors (Unable to find include template ...)

Symptoms:

  • Unable to find include template with name 'radarr-quality-definition-movie'
  • Similar errors for other include: - template: entries

Cause:

  • You are using an older pre-v8 recyclarr.yml that still has include: - template: entries.
  • Recyclarr v8 removed official include templates from the default provider.

Fix (v8 path used by this repository):

# 1) Regenerate recyclarr config from the v8 template
./substitute_env.sh docker-compose-files/arr-stack/config/recyclarr_template.yml /opt/arr-stack/appdata/recyclarr/recyclarr.yml

# 2) Regenerate compose and recreate recyclarr (v8.6.0 pin)
./substitute_env.sh docker-compose-files/arr-stack/template.yaml docker-compose.arr-stack.yml
docker-compose -f docker-compose.arr-stack.yml pull recyclarr
docker-compose -f docker-compose.arr-stack.yml up -d recyclarr

3. qBittorrent/SAB not using VPN IP

In this template, only download clients are expected behind VPN tunnel.

  • Behind VPN: gluetun, qbittorrent
  • Not behind VPN by default: radarr, sonarr, prowlarr, bazarr, emby, seerr

Checks:

docker logs gluetun | grep -i connected
docker exec qbittorrent curl -s ifconfig.me

4. Dependency/healthcheck startup failures

Symptoms:

  • dependency failed to start
  • Services wait forever on service_healthy

Checks:

docker inspect gluetun | grep -A 20 Health
docker inspect prowlarr | grep -A 20 Health
docker logs <container_name>

Fixes:

  • Increase start_period for slow-start services.
  • Test healthcheck command inside the container.
  • Validate generated compose YAML syntax with docker-compose ... config.

5. Network communication failures

Checks:

docker network inspect vpn_network
docker exec radarr wget -qO- http://prowlarr:9696/ping

Fixes:

  • Ensure relevant services are on vpn-network in generated compose.
  • Recreate stack networks:
docker-compose -f docker-compose.arr-stack.yml down
docker network prune
docker-compose -f docker-compose.arr-stack.yml up -d

5b. qBittorrent stuck in "Created" / network service:gluetun not found

Symptoms:

  • qbittorrent does not start after updates and remains in Created
  • Recreate shows: Error response from daemon: network service:gluetun not found

Cause:

  • network_mode: service:gluetun can fail during single-service recreate/update flows on some Docker setups.

Fix:

# Ensure template uses container mode for qbittorrent:
# network_mode: "container:gluetun"

# Re-render compose and recreate the pair
./substitute_env.sh docker-compose-files/arr-stack/template.yaml docker-compose.arr-stack.yml .env
docker-compose -f docker-compose.arr-stack.yml up -d --force-recreate gluetun qbittorrent

Verify:

docker-compose -f docker-compose.arr-stack.yml ps gluetun qbittorrent
docker-compose -f docker-compose.arr-stack.yml logs --tail 100 qbittorrent

5c. qBittorrent HTTPS 502 after stack updates

Symptoms:

  • qBittorrent opens via HTTPS reverse proxy URL and returns 502 after stack updates/recreates.

Common causes:

  • gluetun and qbittorrent are out of sync after recreate/update and need a paired restart.
  • qBittorrent WebUI can be left in HTTPS mode while your reverse proxy still targets HTTP upstream.

Fix:

# 1) Recreate the VPN + torrent pair together
docker-compose -f docker-compose.arr-stack.yml up -d --force-recreate gluetun qbittorrent

# 2) If 502 persists, force qBittorrent WebUI back to HTTP
CONF="$(find /opt/arr-stack/appdata/qbittorrent -type f -name 'qBittorrent.conf' | head -n1)"
echo "Using config: $CONF"
sudo sed -i 's#^WebUI\\HTTPS\\Enabled=.*#WebUI\\HTTPS\\Enabled=false#' "$CONF"
sudo sed -i 's#^WebUI\\HTTPS\\CertificatePath=.*#WebUI\\HTTPS\\CertificatePath=#' "$CONF"
sudo sed -i 's#^WebUI\\HTTPS\\KeyPath=.*#WebUI\\HTTPS\\KeyPath=#' "$CONF"
docker-compose -f docker-compose.arr-stack.yml restart qbittorrent

Verify:

docker-compose -f docker-compose.arr-stack.yml ps gluetun qbittorrent
docker-compose -f docker-compose.arr-stack.yml logs --tail 120 qbittorrent

6. Data/config persistence problems

Checks:

docker inspect radarr | grep -A 20 Mounts
ls -la /opt/arr-stack/appdata/radarr

Fixes:

mkdir -p /opt/arr-stack/appdata/radarr
chown -R <PUID>:<PGID> /opt/arr-stack/appdata/radarr

For stacks that use named volumes, inspect them with docker volume ls rather than changing ownership on an old host appdata directory.

7. Caddy reverse proxy issues

If Caddy refuses to start, validate syntax first:

./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
docker compose -f docker-compose.caddy.yml run --rm caddy \
  caddy validate --config /etc/caddy/Caddyfile

If validation reports open /etc/caddy/Caddyfile: permission denied, the template renderer created the generated file with mode 600, and the container's mapped user cannot read it. Apply the chmod 644 command above. The generated Caddyfile does not contain the Cloudflare token. Do not apply that mode to docker-compose.caddy.yml, which does contain the rendered token.

If logs report mkdir /data/caddy: permission denied or mkdir /config/caddy: permission denied, the generated Compose file still uses host bind mounts that are not writable through Docker's user-namespace mapping. Re-render it from the current Caddy template, which uses named volumes, then recreate the container:

./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

The same risk applies to any writable bind mount when Docker uses rootless or user-namespace mapping. The Caddy, Beszel Agent, PostgreSQL/pgAdmin, Technitium, Gramps Web, Node-RED, Home Assistant, and Mosquitto templates use named volumes for application state to avoid this issue.

Arr Stack, Gramps media, Vault config/TLS, and other explicit external storage mounts intentionally remain host-managed. Verify their ownership and access on the target Docker installation; do not replace those mounts with named volumes without planning configuration edits, backups, and existing-data migration.

Before changing an existing bind-mount deployment to named volumes, stop the stack and copy its current data. Starting against a new empty named volume does not import the old bind-mounted data automatically.

If certificate issuance fails with errors like:

  • could not determine zone for domain ... / expected 1 zone, got 0
  • dial tcp 1.0.0.1:53: i/o timeout

these are two distinct failure modes with the same underlying cause: Caddy's DNS-01 resolver cannot reach 1.1.1.1/1.0.0.1 on port 53.

Distinguish the two errors:

Error Meaning
expected 1 zone, got 0 Caddy reached a resolver, but it was Technitium (split-DNS). NAT exception misconfigured or alias IP wrong.
dial tcp 1.0.0.1:53: i/o timeout Caddy's packets are being dropped. A firewall block rule on OPT2 is above the pass rule, or the NAT exception is not set up at all.

Check DNS reachability from the Caddy host:

# Must return Cloudflare SOA, not Technitium SOA (dns01.home.example.com)
dig +short SOA home.example.com @1.1.1.1

# Distinguish firewall drop from redirect:
dig @1.1.1.1 +tcp +time=5 google.com   # timeout = firewall drop
dig @1.1.1.1 +notcp +time=5 google.com # timeout = same; success = TCP-specific block

Keep public resolvers in the Caddy TLS block — do not switch to Technitium as a workaround, as it causes expected 1 zone, got 0 because Technitium is authoritative for the internal zone:

tls you@example.com {
  dns cloudflare {env.CLOUDFLARE_DNS_TOKEN}
  resolvers 1.1.1.1 1.0.0.1
}

If your network forces DNS redirection, exclude Caddy from that redirect and allow it to query public resolvers on TCP and UDP port 53.

If a proxied app fails over HTTPS with self-signed backend certs, ensure the target block uses:

transport http {
  tls_insecure_skip_verify
}

Update flow (safe)

  1. Generate compose files from templates.
  2. Validate with docker-compose -f <file> config.
  3. Pull images.
  4. Recreate services.
  5. Review logs after startup.
docker-compose -f docker-compose.arr-stack.yml pull
docker-compose -f docker-compose.arr-stack.yml up -d

Last Updated: 2026-02-14