Updating
docker compose pull && docker compose up -d
That is the whole update. Database migrations run when the api container starts, so there is no separate migrate step to remember.
- Glance that last night's dump in
data/backups/daily/is there and not tiny. - Run the two commands above.
- Give the api up to a minute to turn healthy, then check the environment chip in the app.
The same command is covered in Operating. Below: what happens underneath, and how to pin a version.
What happens on the api's first boot after an update
pullfetches any newer images.up -drecreates the containers that changed and leaves the rest alone.- Before it serves traffic, the new api container checks the platform database is reachable, applies any pending platform migrations, loads the installed modules, and then reconciles each workspace's schema (a module that shipped a new migration since a workspace enabled it gets caught up here). Only after that does it start listening.
- If a migration fails, the boot fails on purpose. A boot that stays unhealthy past the 60-second grace period is telling you the update did not apply cleanly. See Rollback.
Why a failed migration stops the boot
A half-migrated database is worse than an api that is briefly down, so Cobblr would rather stop loudly than serve against a schema it only half-changed. The container's healthcheck gives this startup a 60-second grace period before it counts as unhealthy, which is enough for the migration and module-load work above.
The database upgrades itself, so check the night's dump first
- When a new release moves to a newer Postgres major version, the data
directory is upgraded in place on boot, so a plain
docker compose pull && docker compose up -dcarries you across major versions with nothing to run by hand. - That is the riskiest thing the stack ever does unattended, and the
mitigation is already on disk: the
nightly dump in
data/backups/daily/. Before an update, glance that the newest file is from last night and not tiny. Thirty seconds, and a failed in-place upgrade becomes a restore instead of a loss.
Choosing a channel
By default the images track latest. COBBLR_VERSION in your .env points
every service at a different tag instead. Either way,
docker compose pull && docker compose up -d then pulls exactly that tag for
every service.
-
nightlycarries what landed the day before, and is the channel the setup builder recommends while Cobblr is in active development, paired with the updater below:# .envCOBBLR_VERSION=nightly -
latestfollows numbered releases only, for a box that should move a few times a month rather than most days. -
A pinned release is how you control when you move, and it is the same lever Rollback uses to step back to a known-good version:
# .envCOBBLR_VERSION=2026.8.0
What nightly carries, and how versions are numbered
Most days there is a new nightly. A quiet day, or a build that does not come out clean, means there isn't. Track it if you want changes as they land and do not mind the occasional rough edge.
Versions are dated rather than semantic: the year, the month, then a counter within that month.
Update automatically
Turn on the autoupdate profile and the stack checks for newer images on
its own and restarts only the containers whose image changed:
# .env
COMPOSE_PROFILES=caddy,autoupdate
COBBLR_VERSION=nightly
Then docker compose up -d once, to start the updater. The stack ships with
it off, and the setup builder ticks "Update automatically"
for you whenever you choose the nightly channel.
- Keep
caddyin the list if the bundled proxy is running.COMPOSE_PROFILESis the whole list of optional services, soCOMPOSE_PROFILES=autoupdateon its own would also stop the proxy at the nextup -d. - It pairs best with
nightly. Onlatestit does no harm and picks up each numbered release the night it lands. On a pinned version it never finds anything to do. - It checks every four hours, in UTC, rather than at one hour of one clock. Any fixed hour is only ever right for one part of the world, and a box whose local morning happens to fall before the day's build is published would check just too early and sit a day behind, every day. An interval asks nothing about your timezone and cannot go stale that way.
- Four-hourly checking is still about one restart a day. There is roughly one new build a day, so five of the six checks find nothing and touch nothing. A check that finds no new image does not restart anything.
- Pick your own moment if you would rather.
WATCHTOWER_SCHEDULEtakes a six-field cron expression (seconds first), for example0 0 8 * * *for 08:00, andWATCHTOWER_TZ=Europe/Berlinreads it in your zone instead of UTC. Worth choosing an hour you are awake for: an update that goes wrong at 04:00 sits wrong until you get up. - Only Cobblr's containers are touched. The updater acts on containers carrying Cobblr's label, so other stacks on the same box are left alone.
- The dump keeps its own clock. The backup container dumps every 24 hours
counted from the last time it started, so it is not synchronised with the
update. Before you switch the updater on, check that
data/backups/daily/holds a recent file that is not tiny, because from then on the updates happen without you.
What the updater is, and what it needs
It is Watchtower, a small container that
watches the Docker socket, compares each running image against the registry
on a schedule, and does the equivalent of docker compose pull && docker compose up -d for whatever moved. It needs the socket
(/var/run/docker.sock) mounted, which is the same access the docker
command on the host has. That is why it is a profile you turn on rather than
part of the default stack.
The compose file also pins DOCKER_API_VERSION. Watchtower asks for a very old
Docker API by default, and every engine since version 25 refuses it, so without
the pin the updater stops seconds after it starts while the rest of the stack
looks perfectly healthy. You would only ever raise it, and only for an engine
older than Docker 20.10.
Which container comes back first is not promised, and it does not need to be: every service has a restart policy, and the api will not serve until the database answers and any migration the nightly carries has applied, exactly as after a hand-run update. If it comes up before the database it restarts and tries again. Updates stay a one-way door, so Rollback is the same restore it always was.
After the update
Open the app and check the environment chip in the interface: it reads the running build from the api's health endpoint, so a stale tab can tell it is behind. If something looks off, Health and status shows how to read the health endpoint and the container state directly.