Viewer guide
Upgrading
Replace the container with a newer image. Everything you keep is in the data folder, and the new version picks it up where the old one left off. Take a backup first: migrations only go forward.
Upgrading is replacing the container. Everything you keep lives in VIEWER_DATA_DIR, and the new version picks it up where the old one left off.
Take a backup first#
The database brings itself up to date at startup. Tables are created with CREATE TABLE IF NOT EXISTS on every boot, so a table a new version adds appears on its own. Beyond that, versioned migrations run at boot too, tracked in the database's own version counter and applied at most once, ever.
There is no down migration. A backup is the only way back, so take one before you start.
Which version you are running#
The Viewer does not report a version of its own. Its screens do not show one, the health endpoint does not return one, and the startup log names only the bridge version. What identifies a running container is the tag you started it from, plus the commit baked into that image as a label:
docker inspect --format '{{.Config.Image}}' desde-viewerThat is the tag the container was started from. The commit it was built from is a label on the image:
docker inspect --format '{{json .Config.Labels}}' desde-viewerLook for org.opencontainers.image.revision in the output. Keep the tag written down outside the container too, in the same place as the rest of the run command, because a container you have already replaced cannot tell you what it used to be.
Which tag you are on decides what upgrading means#
Four kinds of tag are published to the image's package page:
latestmoves only when a release is tagged. It never follows day to day development.- A version, like
0.1.12, is one release and never moves. mainis rebuilt every time the project changes. This is the edge of development, not a release.sha-abc1234is one exact commit and never moves.
There is no floating 0.1 tag. A version pin is exact, which is the point of pinning: pulling again gets you the same bytes, and upgrading is a decision you make by editing the tag.
So there are two shapes of upgrade, and only one of them is a pull.
If you pinned a version, change the tag in your run command to the new one. Pulling your existing tag again will do nothing, because that tag is frozen.
If you are on latest or main, pull the tag again to move it:
docker pull ghcr.io/desde-design/viewer:latestReplace the container#
Stopping the container ends any build that is running at that moment, and that build is recorded as failed rather than left half done. Nothing else is lost, and you can run it again afterwards. If a teammate is mid review, pick a quiet moment anyway: everything they have already posted is saved, but the prototype they are looking at goes away until the new container is up.
docker stop desde-viewer && docker rm desde-viewerThen re-run the docker run command from Deploy the Viewer, with the tag you want. Because all state is in the volume, the new container picks up exactly where the old one left off. Keep the same -v volume, the same --env-file, and the same published ports.
If you run it from a compose file instead, the same two shapes apply: docker compose pull && docker compose up -d for a moving tag, or edit the image tag in the file and then docker compose up -d.
Check it came up before you walk away:
curl http://localhost:3100/api/v1/healthSessions survive the upgrade as long as the secret that signs them does not change. If you never set VIEWER_SESSION_SECRET, the Viewer generated one on its first boot and saved it in config.json in the data volume, so keeping the same volume keeps everyone signed in. If you did set it, carry the same value across: a new value signs everyone out.
Rolling back#
Starting an older image against a database a newer one has already migrated is not blocked, and it is not safe. The older build simply finds no migrations it recognizes as pending and boots, on a database shaped for the version you just left. Nothing warns you.
So a rollback is a restore, not a downgrade. Stop the container, put the backup you took back in place as described under Backups, and start the older tag against that. Anything posted between the backup and the rollback is gone, which is the real reason to take the backup immediately before upgrading rather than the night before.
If you do not run the published image#
Two other shapes, and both end in the same "replace the container" step above.
Running from a checkout, with no container at all: updating is a pull, a rebuild and a restart. See Run without Docker.
Building your own image: update the checkout and rebuild before you stop anything, so the new image exists before the old container goes away.
git pullDOCKER_BUILDKIT=1 docker build -f viewer/Dockerfile -t desde-viewer .The image installs its own dependencies, so there is nothing to install on the host first. If you changed the bridge source yourself, rebuild the bridge before this step. Build the image yourself explains that, and why the build runs from the repo root. Then replace the container exactly as above.
Upgrading from before instance membership existed#
If your instance predates instance roles (Admin / Editor / Viewer), migration 1 backfills them the first time you boot the new version:
- the oldest human account becomes Admin: the local-operator row (
operator@localhost) is never counted as "oldest" here, even though it is usually the very first row a zero-config instance ever wrote; - the local-operator row itself is separately set to Admin, since it is admin by definition;
- every other existing account becomes Editor: this preserves what they could already do (create and manage projects), since there were no per-project roles to narrow it from.
Both promotions are logged to stdout so you can see exactly which account(s) just gained instance authority. Review Settings → Members after upgrading and adjust anyone who should not have Admin or Editor.
Before upgrading a production instance, it's worth setting VIEWER_ADMIN_TOKEN (if you haven't already) as a recovery path: it's a bearer credential independent of the migration's account role, so you can still manage members and fix a role from the API even if the backfill did not land the way you expected.