compose.yaml.
Start here: service status
db, api and ui reports a health state, and Compose starts them in order — db, then api, then ui, then nginx. A service stuck in starting or marked unhealthy blocks everything after it, so work on the earliest unhealthy service first. A later service that never started is usually a symptom, not the cause.
For the logs of a single service:
Upgrade fails with no space left on device
sudo docker compose pull downloads a complete new set of images before the old ones are released, so an upgrade briefly needs room for both versions at once. On a host that has been upgraded a few times, it is usually the accumulated images from previous releases that fill the disk rather than the new release being large.
A pull that runs out of space fails partway through with no space left on device or a write error on one of the layers. Nothing is broken by this — the running deployment is untouched and the pull can be repeated once space is available.
Check what is actually full
df reports the filesystem holding Docker’s data; docker system df breaks that down into images, containers, volumes and build cache, with a RECLAIMABLE column showing how much can be freed without touching anything in use.
Docker’s data directory is not always
/var/lib/docker. Confirm it with sudo docker info | grep "Docker Root Dir" and run df -h against that path.Reclaim space
Start with the safe option — this removes only dangling images, the untagged layers left behind by previous pulls:If that did not free enough
Watch it during the pull
If space is tight enough that you expect the pull to be close, watch the filesystem from a second terminal while it runs:nginx exits immediately on startup
The certificate and key are bind-mounted from host paths recorded in.env. If either path does not exist or is not readable, nginx fails at startup rather than serving without TLS.
Check the paths Compose is using, then confirm both files exist on the host:
.env and recreate the container so the new bind mount applies — a plain restart keeps the old mount:
Port already in use
nginx is the only service that publishes a host port, on the address and port you chose during setup (BIND_ADDRESS and BIND_PORT in .env, default 0.0.0.0:443). If something else on the host already holds that port, the container cannot bind and Compose reports an allocation failure.
Find the conflict:
BIND_PORT in .env and recreate nginx. Note that binding below port 1024 requires the Docker daemon to run as root, which is why the install steps use sudo.
The database never becomes healthy
- Missing or empty
db/secrets.txt. It is passed in as a Compose secret and holds the key generated byenv.sh. If setup was interrupted, the file may be absent. - Permissions on the data directory, which typically follows a restore where ownership was not preserved. See Backup & restore.
The API never becomes healthy
The API healthcheck calls/api/v1/misc/ping inside the container. Reproduce it directly to separate an API problem from a proxy problem:
.env.
The page loads but every request fails
This is the signature of a URL mismatch.env.sh writes the address you gave it into several variables (CYMPH_APP_BASE_URL, NEXTAUTH_URL, NEXT_PUBLIC_WEBAPP_URL, CYMPH_BACKEND_API_ENDPOINT, NEXT_PUBLIC_BACKEND_API_ENDPOINT) and into the nginx server_name. If you later change the hostname, port or scheme of the deployment, all of them have to agree — the browser is told to call an API address that no longer answers.
Cannot reach the deployment at all
Work outward from the host:- On the host, confirm the port is listening:
sudo ss -tlnp | grep :443 - From the host, confirm TLS answers:
openssl s_client -connect localhost:443 </dev/null - From a client, confirm the host firewall and any network firewall permit the port.
BIND_ADDRESS is set to 127.0.0.1 rather than 0.0.0.0, the deployment is only reachable from the host itself.

