Install and startup
One container is unhappy and the rest wait on it. The db starts, api runs
migrations against it, and web proxies to api, so a failure upstream leaves
the containers below it stuck. Read from the bottom of the chain up: start with
docker compose logs db, then api.
The api container restart-loops on a config error
The log names the offending value:
Invalid environment:
JWT_SECRET: String must contain at least 16 character(s)
-
Set the named value in
.env. Two secrets are required and must each be at least 16 characters:JWT_SECRETandTENANT_CREDS_ENCRYPTION_KEY. -
Regenerate the file with the setup builder, or set a strong value yourself:
openssl rand -hex 32 -
Before you replace
TENANT_CREDS_ENCRYPTION_KEY, read the warning in Data and backups: once it has encrypted stored credentials, changing it makes those unreadable.
Why it exits, and why a value goes blank
On startup the api validates its environment and exits if anything required is
missing or malformed. That is deliberate: a half-configured instance fails loudly
at boot rather than misbehaving later. The builder generates both secrets, so a
blank or truncated value usually means the .env was hand-edited or a
copy-paste dropped characters.
A migration failed on boot
The container restart-loops, and docker compose logs api shows which migration
file failed and the SQL error under it.
- Check
df -hfor space. A disk that filled mid-migration is a usual cause. - Confirm you pulled the current images (
docker compose pull). An image and a data directory from mismatched versions is another. - Read the SQL error in the log. A hand-modified database is the third usual cause.
- Since your data is in
./data/db, you can restore a pre-update copy and try again (see Data and backups).
How migrations run
Database migrations run automatically when the api container starts, before it
serves any request. Each migration runs in its own transaction, so a failure
rolls itself back and the api exits rather than leaving the schema half-applied.
On a stock install this should not happen, because migrations are tested against
the schema they ship with.
Port already in use
docker compose up reports a bind error because some other process holds the
port the web container wants (8088 by default):
Error: ... failed to bind host port 0.0.0.0:8088 ... address already in use
Either stop whatever holds it, or move Cobblr to a free port:
-
Set
WEB_PORTin.env:WEB_PORT=8090 -
Bring the stack back up:
docker compose up -d -
Open the box at the new port.
See what else is listening with sudo lsof -i :8088 (or ss -ltnp | grep 8088).
Image pull is denied or unauthorized
Error response from daemon: ... denied / unauthorized
The images are public, so a pull needs no login and no account. That message almost always means the image reference is wrong rather than that you lack access.
-
Check the image lines in your
docker-compose.ymlagainst the one the setup builder generated. -
Check
COBBLR_VERSIONin your.envis a tag that exists (stable,nightly, or a release like2026.8.0). A typo there reads to the registry as an image you cannot see. -
If the reference is right, the box may be behind a proxy or a firewall that blocks
ghcr.io. Test the path on its own:docker pull ghcr.io/cobblrhq/cobblr-api:stable
- The one case where a login is genuinely required: if you were given
access to a private pre-release lane,
docker login ghcr.iowith your GitHub username and a personal access token carrying theread:packagesscope.
Nothing is obviously wrong, but the page won't load
Check the order of the chain.
docker compose psshould showdbhealthy,apiup, andwebup.- If
webis up but the browser shows nothing, the api may still be finishing migrations on a first run. Give it a moment and re-run the health check. - If you run HTTPS, the
caddycontainer also has to be up and to have a certificate. That path is covered in HTTPS and access.