Install and startup
The db starts, api runs migrations against it, and web proxies to api.
A failure anywhere upstream leaves the containers below it stuck, so 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
Fix: set the value the log names. On startup the api validates its environment and exits if anything required is missing or malformed, and the log names the offending value.
Invalid environment:
JWT_SECRET: String must contain at least 16 character(s)
- Read the log:
docker compose logs api. - Set a strong value for the named secret, or regenerate the
.envwith the setup builder:
openssl rand -hex 32
- Two secrets are required, each at least 16 characters:
JWT_SECRETandTENANT_CREDS_ENCRYPTION_KEY. - The builder generates both, so a blank or truncated value usually means
the
.envwas hand-edited or a copy-paste dropped characters. - 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 the api exits instead of running anyway
That is deliberate: a half-configured instance fails loudly at boot rather than misbehaving later.
A migration failed on boot
Fix: check disk space and image versions, then read the SQL error. The
container restart-loops, and docker compose logs api shows which migration
file failed and the SQL error under it.
- Check
df -hfor space, because 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 usual cause, as is a hand-modified database. - Read the SQL error in the log.
- Since your data is in
./data/db, you can restore a pre-update copy and try again (see Data and backups).
How migrations run, and why this is rare
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
Fix: free the port, or move Cobblr to another one. If
docker compose up reports a bind error like address already in use, 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 set WEB_PORT in .env and bring the stack
back up:
WEB_PORT=8090
docker compose up -d
Then 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
Fix: check the image reference, not your access. The images are public, so a pull needs no login and no account:
Error response from daemon: ... denied / unauthorized
- Check the image lines in your
docker-compose.ymlagainst the ones the setup builder generated. - Check
COBBLR_VERSIONin your.envis a tag that exists (stable,nightly, or a release like2026.8.0), because 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
Fix: check the order of the chain. docker compose ps should show db
healthy, api up, and web up.
webup but the browser shows nothing? The api may still be finishing migrations on a first run, so give it a moment and re-run the health check.- Running HTTPS? The
caddycontainer also has to be up and to have a certificate, and that path is covered in HTTPS and access.