Operating your instance
Day-two care: keeping it current, keeping copies of your data, and the three things that most often go wrong.
Updating
docker compose pull && docker compose up -d
It pulls any newer images and recreates the containers. Database migrations run automatically when the api container starts. To have this happen on its own, see Updating.
Backups
Sync data/backups/ (and data/files/) somewhere off the box on a schedule.
- The stack already dumps the database nightly into
data/backups/(30 daily and 12 weekly files kept). Your job is getting a copy off the box, because a backup beside the data dies with the disk. - Your data lives in bind-mounted folders under one data root
(
COBBLR_DATA_ROOT,./databy default): Postgres, uploaded files, nightly dumps, installed modules, and Caddy certs. If you pointedCOBBLR_DATA_ROOTat a data disk or a NAS mount, that path is the one to protect. - A full-instance copy of the whole
./data/tree is only a valid backup with the stack stopped. A live copy of a running Postgres directory is a torn snapshot. - For a hand-run dump at any moment:
docker compose exec db pg_dumpall -U cobblr | gzip > cobblr-backup-$(date +%F).sql.gz
Workspace-level backups (restorable snapshots of one workspace, and off-box destinations like Google Drive) are covered in Backup and export.
Stopping, starting, restarting
Ordinary operations are plain compose:
docker compose stopparks the stack with data untouched.docker compose up -dbrings it back.docker compose restart apibounces one service.down(without-v) also just stops. Your data is in bind mounts, so no compose command short of your ownrmdeletes it.
Moving to new hardware
A move is a copy of three things: the .env, the docker-compose.yml, and the
./data/ tree, taken while the stack is stopped.
- On the old box:
docker compose down, then copy the folder over (rsync -athe directory containing the compose file). - On the new box: install Docker,
docker compose up -din the copied folder. The images re-pull by themselves, and they exist for both amd64 and arm64, so moving between a PC and a Pi is the same move. - The
.envmatters most:TENANT_CREDS_ENCRYPTION_KEYmust travel or stored integration credentials become unreadable, andPOSTGRES_PASSWORDmust travel or the copied database refuses the new stack.
If the new box also changes the address, see the next section.
Changing the address later
- Edit
COBBLR_SITE_ADDRESSin.env(and the HTTPS block if the mode changes). docker compose up -d. The proxy re-issues for the new name.
Two things do not follow automatically: bookmarks, and printed QR labels, which carry the old base URL. The QR pages cover printing against a redirect so a future move never costs a re-print. If labels already carry the old name, stand up a redirect at it.
Uninstalling
- Take a copy of
data/backups/first if there is any chance you will want the records back. docker compose downstops and removes the containers.- Delete the compose folder,
./data/included. Everything Cobblr ever wrote lives in that folder, so this is the complete uninstall. docker image prune(ordocker rmion the cobblr images) reclaims the image space.
Troubleshooting
- A device can't reach it (Tailscale). First confirm that device is signed into
the same tailnet. What to check next depends on which Tailscale setup you chose.
On the own-hostname setup (
COBBLR_TLS_MODE=tsnet), the bundled proxy is the tailnet node: checkdocker compose logs caddyfor the one-time approval link on a first run, and confirm the node still shows as authorized in the admin console (a tsnet node signs itself out after about six months unless you disable its key expiry). On the server's-own-name setup,tailscale serve statuson the box should show the app on127.0.0.1:8088, and reaching it from off the tailnet needstailscale funnel, notserve. - Phone can't reach the public (Cloudflare) name, laptop can. Some routers and Pi-hole setups block public names that resolve to private IPs ("DNS rebind protection"). Add an exception for the name, or add a local DNS entry mapping it to the LAN IP. This only applies to the internet-facing paths, since Tailscale doesn't use public DNS at all.
- Certificate didn't issue (Cloudflare, or a legacy DuckDNS setup). Check
docker compose logs caddy. The usual causes: a wrongCLOUDFLARE_API_TOKEN(orDUCKDNS_TOKEN), or the box has no outbound internet to answer the ACME challenge. Certificates cache under./data/caddyand renew themselves about every 60 days with the same DNS token, so a revoked token surfaces weeks later as an expiry. SetCOBBLR_ACME_EMAILand Let's Encrypt emails you before a failed renewal becomes an outage. - Locked out: signup is closed and nobody can get in. Closed signup plus no
operator is recoverable, because you own the box: everything is one
.envedit away. No accounts at all? SetPUBLIC_SIGNUP_ENABLED=true, rundocker compose up -d, register, then close it again. Have an account but no/admin? Put that account's email inSUPERADMIN_EMAILSanddocker compose up -d. The operator check reads the list live, so the existing account is blessed retroactively. Once you're the operator, add people through signup invites instead of reopening signup. - Camera still blocked. Confirm the address bar shows
https://with no warning. On the offline CA option, the certificate must be trusted, not just installed. On iOS that's the extra toggle under Certificate Trust Settings.