Troubleshooting
Common problems with webhooks, SSH, deploys, and state — and how to fix them.
Webhook not firing
Symptom: Push a commit, nothing happens in the daemon logs.
-
Check GitHub webhook delivery history: your repo → Settings → Webhooks → click your webhook → Recent Deliveries. Look for errors or non-2xx responses.
-
Check daemon logs for incoming webhook events. If nothing appears, the request is not reaching the daemon.
-
Verify port 8080 (or your configured
DEEPLO_WEBHOOK_PORT) is reachable from GitHub's IPs. If you're behind NAT, use polling mode or set up a reverse proxy. -
Check that
DEEPLO_GITHUB_WEBHOOK_SECRET_FILEcontains exactly the same secret set in GitHub. A mismatch causes all requests to return 401 and be silently discarded. -
Verify the webhook is configured for push events and the branch in the push event matches the
branchin your repo config. -
Check
watch_paths— if no changed file in the push matches the project'swatch_paths, the deploy is intentionally skipped. Check the logs for "no projects matched" or "skipped: no paths match".
Poller not detecting changes
Symptom: Poll mode is configured but deploys aren't firing when commits land.
-
Check poll state file:
DEEPLO_DATA_DIR/state/poll/<repo>-<branch>.json. Thelast_seen_shafield shows what the poller last observed. -
If
last_seen_shais current but no deploy fired, deduplication may have skipped it. CheckDEEPLO_DATA_DIR/state/projects.jsonfor an existing success record for that SHA. -
Verify the poller is running in the logs:
poller startedandpoller: started repo=... interval=.... -
Check
poll_interval— with the default of60s, a deploy can be delayed up to a minute.
Host key verification failure
Symptom: Deploy fails with ssh: host key mismatch or ssh: unknown host.
-
The host's SSH key changed (e.g. the server was rebuilt). Update known_hosts:
ssh-keyscan 10.0.0.10 >> known_hostsRemove the old entry for that host first if it's a key replacement, not an addition.
-
The wrong known_hosts file is configured. Verify
DEEPLO_SSH_KNOWN_HOSTSpoints to the file that was populated withssh-keyscan. -
The known_hosts file is not mounted into the container. Check the volume mapping in
docker-compose.yml. -
If you're using
DEEPLO_SSH_HOST_KEY_POLICY=accept-new(default) and the key changed, deeplo will reject the connection because the stored key no longer matches. Rerunssh-keyscanto update the stored key.
SSH authentication failure
Symptom: Deploy fails with ssh: unable to authenticate or permission denied (publickey).
-
Verify the private key matches the public key installed on the target host.
-
Verify the key file is mounted and readable:
docker exec deeplo ls -la /run/secrets/deploy_key -
Verify the deploy user exists on the target and the public key is in their
authorized_keys. -
Check the effective SSH user for that host. Deeplo uses
hosts[].userwhen set, otherwiseDEEPLO_SSH_USER. -
Check
DEEPLO_SSH_PORTif the target runs SSH on a non-standard port.
Repo fetch failure
Symptom: Deploy fails with git fetch or git clone errors.
-
Check the repo URL. SSH and HTTPS both work; make sure the URL is correct and the branch exists.
-
For SSH URLs: the private key must have read access to the repository. Test manually:
GIT_SSH_COMMAND="ssh -i /path/to/deploy_key" git ls-remote git@github.com:yourorg/repo.git -
For github.com: ensure
github.comis in your known_hosts file. deeplo uses the sameDEEPLO_SSH_KNOWN_HOSTSfor both host connections and git operations.ssh-keyscan github.com >> known_hosts
Compose preflight failure
Symptom: Deploy fails at the preflight step with a validation error from docker compose config.
-
Missing environment variables — if your compose file uses
${VAR}and the variable isn't set on the remote host, preflight fails. Make sure required variables are available in the remote environment or.envfile. -
YAML syntax error — check the compose file locally with
docker compose config. -
File path issues — check that
repo_subdirandcompose_filesin the project config point to the correct paths within the repo. -
Compose version mismatch — if your compose file uses features not supported by the Docker Compose version on the target, preflight will fail. Update Docker on the target host.
Preflight failures are clean — the live directory on the target is never touched if preflight fails.
Deploy succeeded but app is unhealthy
Symptom: docker compose up succeeded, but docker compose ps shows containers as exited or restarting.
deeplo runs docker compose ps immediately after up and reports a runtime verification failure if any service is not in the running state. This catches containers that start and immediately exit (bad config, missing file, crash on startup).
To debug:
- SSH to the target and run
docker compose logsin the project directory. - Run
docker compose psto see current container states. - Fix the application issue and push a new commit to redeploy.
- Run
deeplo refreshto inspect live runtime state after manual intervention.
Note: deeplo does not perform application-level readiness checks (HTTP probes, etc.), and does not detect containers that crash after starting successfully.
State mismatch after manual intervention
Symptom: deeplo status shows outdated or incorrect information after you ran docker compose commands directly on a host.
Run deeplo refresh to reconnect to all hosts, run docker compose ps for each project, and inspect the live runtime state:
deeplo refreshdeeplo refresh does not rewrite deployment history or create refresh-sourced deployment entries.
Config validation errors
Symptom: deeplo check reports errors.
Common causes:
hosts[].addressis empty or missingprojects[].targetsreferences a host name that isn't in thehostslistrepos[].urlis emptyversionis not1
Run with --probe-hosts to also verify SSH connectivity:
deeplo check --config config.yml --probe-hostsIncreasing log verbosity
Set log level and format via environment variables:
# In docker-compose.yml or EnvironmentFile:
DEEPLO_LOG_LEVEL=debug
DEEPLO_LOG_FORMAT=jsonFor structured output, DEEPLO_LOG_FORMAT=json makes it easier to filter with jq:
docker compose logs -f | jq 'select(.level == "ERROR")'