Skip to content

Upgrade Guide

Upgrade a self-hosted SovEcom stack in five steps: take a backup, pull the new images, apply database migrations, verify the store works, and restore if it does not. SovEcom migrations move forward only. No automated rollback exists, so the backup you take before the upgrade is your only path back to the prior state. Take it every time.

SovEcom runs as a set of Docker images defined in docker-compose.yml: api, admin, setup, storefront, plus postgres, redis, meilisearch, and caddy. You upgrade by replacing the application images and re-running database migrations against the same pgdata volume.

Versioned SQL files under apps/api/src/database/migrations hold the schema, numbered 0000_*.sql through the current head. Drizzle Kit tracks which files have run in the __drizzle_migrations table and a journal at migrations/meta/_journal.json. When you run the migrate command, Drizzle applies only the files that have not run yet, in order. Running it twice is safe. The second run finds nothing to apply.

The migrate command is:

Terminal window
pnpm --filter @sovecom/api migrate:up

That maps to drizzle-kit migrate, which reads DATABASE_URL and applies pending files from apps/api/src/database/migrations.

Some migrations are hand-written and irreversible

Section titled “Some migrations are hand-written and irreversible”

Some migration files are hand-written, because Drizzle’s generator cannot emit triggers, partial unique indexes, or immutability guards. Take 0010_invoice_immutability.sql: it installs a database trigger that forbids DELETE on any invoice row and blocks every UPDATE except the one-time attach of the rendered-PDF pointer. These enforce fiscal retention. You cannot reverse them with a down migration. To get back across such a migration, you restore the full database from your pre-upgrade dump.

Upgrade typeWhat changesMigration riskExtra care
Minor / patchApp image tags, dependency bumps, bug fixes, additive columnsLow. Usually additive, backward-compatible schemaBackup, pull, migrate, verify
MajorBreaking schema changes, base-image jumps (Postgres line, Node line), removed fieldsHigher. May rewrite or drop dataRead the release notes in full. Test the dump restore on a staging copy first. Plan a maintenance window

Both follow the same five steps below. A major upgrade adds two rules. Read the release notes for that version before you start, and rehearse the upgrade against a copy of production before you touch the live store.

  • Pick a low-traffic window. Customers will see errors while the API restarts and migrations run.
  • Confirm your .env still has POSTGRES_PASSWORD and MEILI_MASTER_KEY set. Compose fails fast if either is missing.
  • Have enough free disk for a full database dump plus the new images.
  • Read the release notes for the version you are moving to, especially for a major upgrade.

Take a logical dump of the running Postgres container before you change anything. The database user and name are both sovecom (see docker-compose.yml).

Terminal window
# From the directory holding docker-compose.yml
docker compose exec -T postgres \
pg_dump -U sovecom -d sovecom --clean --if-exists \
> "sovecom-backup-$(date +%Y%m%d-%H%M%S).sql"

--clean --if-exists tells the dump to drop and recreate objects on restore, so you can replay it onto a database that already holds the old schema.

Verify the dump is non-empty and ends cleanly before you continue:

Terminal window
tail -n 5 sovecom-backup-*.sql # should show PostgreSQL dump complete
ls -lh sovecom-backup-*.sql # size should be plausible, not a few bytes

Back up your .env and your docker/Caddyfile at the same time. They are not in the database, and a clean recovery needs them.

Check out the new release (or update your docker-compose.yml image tags), then pull. The compose file builds api, admin, setup, and storefront from local Dockerfiles and pulls postgres, redis, meilisearch, and caddy from registries.

Terminal window
git fetch --tags
git checkout vX.Y.Z # the release tag you are upgrading to
docker compose pull # pulls postgres/redis/meilisearch/caddy
docker compose build api admin setup storefront # rebuilds local images

Do not start the new containers yet. Migrations run before the new API serves traffic.

Stop the API so no old code writes while the schema changes, keep Postgres up, then apply pending migrations.

Terminal window
docker compose stop api admin storefront # keep postgres + redis running

Run migrate:up against the live database. The cleanest way is a one-off container built from the new code, with DATABASE_URL pointed at the postgres service:

Terminal window
docker compose run --rm \
-e DATABASE_URL="postgres://sovecom:${POSTGRES_PASSWORD}@postgres:5432/sovecom" \
api pnpm --filter @sovecom/api migrate:up

Drizzle prints each file it applies. If the database is already current, it applies nothing and exits zero. That is expected when you re-run after a partial failure.

Bring everything up on the new images.

Terminal window
docker compose up -d
docker compose ps # every service should be running / healthy

Run these checks before you call the upgrade done:

  • API health. Confirm the api container is up and not crash-looping: docker compose logs --tail=50 api. Look for a clean Nest boot, no migration or schema errors.

  • Admin loads. Open the admin URL and sign in. The dashboard should render against live data.

  • Storefront renders. Open the storefront, load a category and a product page. Prices show in integer cents formatted to the store currency.

  • Place a test order end to end if the upgrade touched cart, checkout, payments, tax, or invoicing. Confirm the order appears in Admin → Orders, an invoice is issued with the next gapless number, and tax lines match what you expect. See Orders and Payments.

  • Search works. If the release changed indexed fields, reindex Meilisearch:

    Terminal window
    docker compose exec api pnpm --filter @sovecom/api reindex

SovEcom admin — post-upgrade smoke order with tax breakdown

If every check passes, keep the pre-upgrade dump until the store has run clean for a few days, then archive it.

Use this when a migration corrupted data, the new API will not boot, or verification fails and you need the store back now.

Stop the application containers, keep Postgres running, and replay the dump you took in Step 1.

Terminal window
docker compose stop api admin storefront setup
# Replay the dump into the running postgres container
docker compose exec -T postgres \
psql -U sovecom -d sovecom < sovecom-backup-YYYYMMDD-HHMMSS.sql

Because the dump was taken with --clean --if-exists, it drops and recreates objects, returning the schema and data to the pre-upgrade state. After it finishes, check out the previous release tag, rebuild the old application images, and bring the stack back up:

Terminal window
git checkout vX.Y.W # the version you were on before
docker compose build api admin setup storefront
docker compose up -d

Verify with the same checks from Step 4. The store is now running the prior version against the prior schema.

If you took a pgdata tarball (recommended for major upgrades) and the logical replay does not come up clean, restore the raw volume instead. This overwrites the entire database directory, so use it only when you intend to discard the current pgdata.

Terminal window
docker compose down # stop everything, keep volumes
docker volume rm sovecom_pgdata # remove the broken volume
docker volume create sovecom_pgdata # recreate it empty
docker run --rm \
-v sovecom_pgdata:/data -v "$PWD":/backup alpine \
sh -c "cd /data && tar xzf /backup/pgdata-YYYYMMDD-HHMMSS.tar.gz"
git checkout vX.Y.W
docker compose build api admin setup storefront
docker compose up -d
  • Confirm gapless invoice numbering still advances by one on the next real order. The immutability trigger from 0010_invoice_immutability.sql rejects any after-the-fact edit, so you cannot patch a wrong number in place. You correct it with a credit note.
  • Re-check VAT and OSS behaviour if the release touched tax. See Tax.
  • Watch the API logs through the next business day for slow queries or errors that a new migration’s indexes surface.
StepCommand
Back up (logical)docker compose exec -T postgres pg_dump -U sovecom -d sovecom --clean --if-exists > backup.sql
Pull / builddocker compose pull && docker compose build api admin setup storefront
Stop app tierdocker compose stop api admin storefront
Migratedocker compose run --rm api pnpm --filter @sovecom/api migrate:up
Startdocker compose up -d
Reindex searchdocker compose exec api pnpm --filter @sovecom/api reindex
Restore (logical)docker compose exec -T postgres psql -U sovecom -d sovecom < backup.sql

Related guides: Getting Started, Orders, Payments, Tax.