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 .env
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 .env
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.ymlthat still hasinclude: - 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 .env
# 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 .env
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_periodfor 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-networkin 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:
qbittorrentdoes not start after updates and remains inCreated- Recreate shows:
Error response from daemon: network service:gluetun not found
Cause:
network_mode: service:gluetuncan 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
502after stack updates/recreates.
Common causes:
gluetunandqbittorrentare 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, 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 0dial 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)
- Generate compose files from templates.
- Validate with
docker-compose -f <file> config. - Pull images.
- Recreate services.
- 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