LensHub/ docs / upgrades
Self-hosting

Upgrades and migrations

Order matters. Read this before pulling.

The rule

Migrations are applied before the new containers start.

New code expects the new schema. With the bundled compose file this happens on its own: a migrate service applies whatever is pending and exits, and the api and worker wait for it to succeed. If a migration fails, they do not start — docker compose logs migrate says why.

Upgrading

# 1. Back up first. Always.
./scripts/backup.sh

# 2. Pull the new images
docker compose pull

# 3. Start the new containers (migrations run first)
docker compose up -d

Running your own compose file or orchestrator? Apply migrations yourself before starting the new api and worker:

./services/postgres/migrate.sh --apply        # from a checkout
# or, with only the postgres image:
docker run --rm -e PGHOST=... -e PGUSER=... -e PGPASSWORD=... -e PGDATABASE=... \
  --entrypoint bash <postgres image> /opt/lenshub/migrate.sh --apply

Checking what will happen

./services/postgres/migrate.sh --status

Lists every migration as applied or pending, without changing anything.

Migrations are almost always additive

They add tables, columns and indexes and do not drop your data, which makes most of them safe to apply while the old version is still running. The exceptions say so at the top of the file — for example, removing a column nothing reads any more (055_drop_contexts_visibility.sql) breaks an older worker still running against the database, so start the new containers straight after.

Some build indexes concurrently, which takes minutes on a large database but does not block reads or writes. None rewrite a large table under a lock: a new column that needs values for existing rows is filled in the background in small batches (for example context_versions.body_hash, which the worker backfills after 054).

After upgrading

docker compose ps                       # everything healthy
curl http://localhost:8080/readyz       # {"status":"ok"}

Then confirm a search still returns results — schema and code agreeing is necessary but not sufficient.

Rolling back

Most migrations are additive, so the previous version generally runs against the newer schema without complaint. Restore from a backup if it does not (see Backups).

Upgrade a staging instance first if you have one. If you do not, take the backup in step 1 seriously — it is the whole rollback plan.

On this page