Upgrading

Migrations first, in every shape, and the two things that make an upgrade unrecoverable rather than annoying.

An upgrade is: new image, migrations applied, server ready. The app checks and applies pending migrations on every startup, before serving requests, whether its PostgreSQL is embedded or external.

While the version is 0.0.x, nothing in the application is promised to stay put. Read the release notes before upgrading a deployment other people use.

The short version, per shape

The account menu — the avatar in the top right — shows the version you are running and checks for a newer one when you press the button. What it tells you to do about it is one of these.

You run Upgrading is
The desktop app Nothing. It updates itself: it offers the new version and installs it when you accept.
bunx @datagripe/cli personal bunx @datagripe/cli@latest personal. The data directory is untouched.
One container docker pull … && docker restart datagripe. It migrates itself on start.
Compose docker compose pull && docker compose up -d. The migration service runs first.
Kubernetes or Helm Upgrade the image or chart, then restart. The app checks migrations before serving; existing hooks and init containers may still pre-apply them. Image tags and pull policy determine which version starts.
A checkout git pull && bun install && bun run db:migrate.

The rest of this page is what those commands are doing, and the two things worth doing before any of them.

Back up first

Two things make an upgrade unrecoverable rather than annoying, and both are cheap to avoid.

The database. The durable state is the application PostgreSQL: accounts, sessions, workspaces, datasource metadata and encrypted secrets, documents, layouts, query history.

pg_dump --format=custom --file=datagripe-$(date +%Y%m%d-%H%M).dump "$APP_DATABASE_URL"

CONNECTION_ENCRYPTION_KEY. The datasource passwords in that dump are encrypted with it. A backup without the key restores everything except the ability to open any datasource. Store the key with the same care as the backup and not in the same place.

Embedded shapes

The personal CLI, the desktop app and the zero-config container migrate themselves on start. Upgrading is replacing the binary or pulling the image.

The desktop app is the one shape that needs nothing from you: it checks its own release feed, offers the new version, and replaces itself when you accept. Declining asks again next time it starts.

bunx @datagripe/cli@latest personal
docker pull ghcr.io/datagripe/datagripe && docker restart datagripe

The data directory is untouched. It is the thing worth backing up.

Shared shapes

The app checks schema_migrations on every startup, in embedded and external database modes, and applies missing files before accepting HTTP or WebSocket connections. Each migration and its history row commit together. A PostgreSQL advisory lock serializes concurrent app starts and manual runners; a failed migration rolls back and stops startup. The app database account must have permission to apply the schema changes.

The image also has a migration-only entry point for an explicit pre-rollout step:

docker run --rm \
  -e APP_DATABASE_URL=… -e CONNECTION_ENCRYPTION_KEY=… -e SESSION_SECRET=… \
  ghcr.io/datagripe/datagripe migrate
Shape Runs the migration as
Compose a one-shot service, before the app
Helm a pre-install,pre-upgrade hook Job
Plain manifests an init container
By hand bunx @datagripe/cli migrate

So in practice: docker compose pull && docker compose up -d, or helm upgrade datagripe oci://ghcr.io/datagripe/charts/datagripe, and the migration is already in the path.

Already-recorded migrations are skipped. Concurrent runners wait on the same advisory lock and recheck history before executing each file.

Restarting into a pulled image

With imagePullPolicy: Always on a tag that moved, an app-container restart may pull the new image. Completed init containers do not rerun, but the app itself checks migration history before serving. The restart server button therefore includes the schema check in both database modes. A pinned image still needs its tag updated to get a new release.

The button requires a supervisor that will restart the process. Kubernetes enables it by default; other supervised shapes can set RESTART_TO_UPDATE=true — see updates.

MCP missing after a 0.0.13 upgrade

Version 0.0.13 silently hid the MCP sidebar header when its status request failed. One cause is a shared app database missing migration 0027: the status query attempted to read the new functionality columns. Apply the pending migrations using the upgraded image or CLI, then reload the app. This does not revoke tokens or reset the project's MCP setting.

The corrected client keeps the launcher visible on status failures and shows status unknown with a retry action. Its management tab reports missing migrations explicitly. If migration 0027 is already applied, check the server log for the failed mcp.status action; a missing header alone does not prove the server is off.

Pin the version

The chart's version is the app's version, and the image's tag should be too. latest is fine for trying it and wrong for a service other people depend on — an upgrade should be a thing you did, not a thing that happened.

helm upgrade datagripe oci://ghcr.io/datagripe/charts/datagripe --version 0.0.16

Restoring

createdb datagripe_restore
pg_restore --dbname=datagripe_restore datagripe-YYYYMMDD-HHMM.dump
APP_DATABASE_URL=postgres://…/datagripe_restore bun run db:migrate

Run the migration after every restore: it is idempotent, and it brings an older dump up to the current schema before the server starts.

Sessions survive a restore. They only become invalid if you also rotate SESSION_SECRET, and the two can coexist safely.

Worth doing quarterly into a scratch database rather than discovering the procedure during an incident. Compare select count(*) from users and select count(*) from connections between the two databases.

Rotating the encryption key

Add the new key as version 2 in the keyring, restart, and re-save connections opportunistically. Old key versions keep decrypting, so there is no flag day and no window where a datasource cannot be opened.

This page as Markdown, for an agent or a diff.