Troubleshooting
Short, symptom-first fixes for the problems seen most often in a real deployment. Each entry gives the symptom, the likely cause, a command to check, and a safe fix. Nothing here asks you to run an action on a stack that is still serving traffic.
For deployment steps themselves, see deployment.md; for the Dockge switch-over, migrating-from-dockge.md.
Sign-in is refused
"Sign-in was blocked because the request did not come from this panel's own page"
- Cause: the browser sent an
Origin(or aRefererthat produced one) that does not match the access path the request arrived on. This is the panel's CSRF/origin protection, not a wrong password. - Diagnose: the panel names both sides in the activity log (
/audit, sign-ins), for exampleorigin blocked: received …, expected …. - Fix: open the panel at the exact address you configured.
- If you set a proxy origin, use that
https://…address, not the SSH tunnel. - If a reverse proxy adds redirects, sign in at the final URL it serves.
- Behind a reverse proxy, make sure it preserves the original
Hostheader (Caddy andcloudflareddo by default).
- If you set a proxy origin, use that
"The sign-in form expired"
- Cause: the double-submit CSRF cookie was missing or different from the token in the form — usually a stale tab, a cookie from an earlier address, or two sign-in tabs open at once.
- Diagnose: check the browser's cookies for the panel's host; the panel sets
cp_csrfandcp_session. - Fix: delete the cookies for the panel's domain only (not all browser data), reopen one tab at the configured address and sign in again. If it persists across tabs, that is the known limitation tracked in roadmap.md ("Login CSRF across tabs and access modes").
The SSH tunnel works but sign-in keeps failing
- Cause: on 0.1.0 a configured
COMPOSE_PANEL_PUBLIC_ORIGINmakes Origin checks strict for that origin, so forms posted throughhttp://127.0.0.1:5081are refused on purpose. This is not a broken tunnel. - Diagnose: the activity log shows a refused sign-in with
expected https://…. - Fix: use one access path at a time on 0.1.0. From 0.2.0 you can keep both:
set
COMPOSE_PANEL_PROXY_ORIGINSandCOMPOSE_PANEL_ALLOW_LOOPBACK=true(ADR 0012, deployment.md).
A stack is missing or shows unknown
-
Cause: the panel matches Docker projects to Compose files by path. If the stacks folder is mounted at a different path inside the container than on the host, or the file has an unsupported name, the match fails.
-
Diagnose:
# What the panel sees: docker inspect compose-panel --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' # What Docker recorded for a stack's container: docker inspect <container> --format '{{index .Config.Labels "com.docker.compose.project.config_files"}}' -
Fix:
- Make host path and container path identical (
/opt/stacks:/opt/stacks) and setSTACKS_DIRto that path. - A stack needs exactly one Compose file per folder (
compose.yaml,compose.yml,docker-compose.yamlordocker-compose.yml). Folders with an override file, several Compose files, or a symbolic link are refused on purpose; see migrating-from-dockge.md.
- Make host path and container path identical (
Logs are empty
-
Cause: the panel reads logs through
docker compose logs. A stack that is not deployed, or whose containers never wrote output, shows nothing. -
Diagnose: compare from the host, in the stack folder:
cd /opt/stacks/<stack> && docker compose logs --tail 200 --timestampsRun this on the host, not inside another container's terminal, where the Compose plugin may be absent.
-
Fix: if the command shows output but the panel does not, reload the page (the panel loads the last
COMPOSE_PANEL_LOG_TAIL_LINESlines when the stack page opens). Use Follow live to stream new lines.
The Health check column is empty
- Cause: Docker has no health check configured for those services. The column is hidden entirely when no service declares one; otherwise a service without a check shows Not configured.
- Diagnose:
docker inspect <container> --format '{{json .Config.Healthcheck}}'(empty/null means none). - Fix: this is not a fault. A
Runningcontainer without a health check can still be unhealthy at the application level; add ahealthcheck:to the service if you want Docker to judge it, then recreate the container with Up.
A .env change has no effect
- Cause: saving only writes the file; running containers keep the
environment they were created with. Restart reuses the same container, so
it keeps the old values too. Less often, the variable is in
.envbut the service never receives it (it is not listed underenvironment:). - Diagnose: the service is marked Recreate pending on the stack page
(Compose 5.5.0 or later). On the host, compare the container's creation time
with the save (
docker compose -p <project> ps), or print only the variable concerned, never the whole environment, which may hold secrets:docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' <container> | grep '^NAME='. - Fix: run Up on the service (or the stack). It recreates the
container with the new values; expect a short interruption for a single
instance. For runtime settings such as PHP directives,
.envalso needs a bridge to the runtime: see integrations/runtime-settings.md.
docker compose works on the host but not in another container
- Cause: the Compose plugin is installed on the host and in the panel image, but not necessarily inside an unrelated container, even when the Docker socket is mounted.
- Diagnose:
docker compose versionin the container's own terminal. - Fix: run Compose commands from the host or from the panel. The panel ships its own Docker CLI and Compose plugin.
Which Caddyfile is actually active?
-
Cause: a Caddy running as a systemd service reads
/etc/caddy/Caddyfile, while a Caddy run as a container/stack reads the file inside that stack (for example/opt/stacks/caddy/Caddyfile). Editing the other one has no effect. -
Diagnose:
systemctl status caddy --no-pager caddy validate --config /etc/caddy/Caddyfile -
Fix: edit the active file and reload:
systemctl reload caddy. Point the panel's site atreverse_proxy 127.0.0.1:5081. A folder with only aCaddyfileis not a Compose stack and will not appear in the panel.
Containers leave zombie processes
-
Cause: a container's process is PID 1 and does not reap orphaned children, so exited children stay as zombies (
<defunct>). -
Diagnose: inside the container,
ps -eo stat,pid,ppid,comm | grep Z, or on the hostdocker top <container>. -
Fix: add
init: trueto the service so a tiny init reaps them, then recreate the container with Up:services: app: image: example/app init: trueinit: truefixes the reaping problem, not every possible cause, and takes effect only after the container is recreated. It is a service setting, so it is edited in that stack's Compose file like any other change.