Self-hostingUpgrades

Upgrades

Five-step upgrade: backup Postgres, pull the GHCR image, confirm health, roll back with the dump.

The image is ghcr.io/epure-sh/epure. There is no worker fleet or Redis stream to drain. Path: backup → pull → watch health. Keep .env; Compose still interpolates it.

Upgrade in five steps

Dump Postgres, note the commit you are leaving, check out the new revision, compose up (pulls GHCR), confirm /health.

Pin production

Default compose uses ghcr.io/epure-sh/epure:latest. In production set EPURE_IMAGE=ghcr.io/epure-sh/epure:vX.Y.Z in .env.

Dump Postgres

docker compose exec -T postgres pg_dump -U epure -d epure -Fc > "epure-$(date +%Y%m%d).dump"

Store the dump off-box. Prod overlay: same exec; Postgres has no host port.

Optional artifacts (if you upload maps):

Archive source-map volume

command

docker volume ls
docker run --rm \
-v epure_artifacts_data:/data \
-v "$(pwd)/backups:/backup" \
alpine tar czf /backup/artifacts-$(date +%Y%m%d).tar.gz -C /data .

Note the git commit you are leaving.

Check out the new revision

git fetch
git checkout <commit>

Pull and start

Pull local stack

command

docker compose up -d

Migrations apply when the epure process starts. There is no separate migrate container.

Confirm health

docker compose logs -f Epure

Check health

Local default. Use EPURE_PUBLIC_URL/health if you changed the port or are on HTTPS.

command

curl -sS http://localhost:8080/health

Expected

{"status":"ok"}

On production, use EPURE_PUBLIC_URL/health. Local default is http://localhost:8080/health. Then: login, Issues list, one store POST → 202. Quickstart Try ingest.

Roll back if it fails

Schema may move forward only. Restore the dump before starting an older binary if a migration is not backward-compatible.

Restore dump and previous binary

Keep Postgres up. Stop only the app, restore, then compose up the previous image.

command

docker compose stop Epure
git checkout <previous-commit>
docker compose exec -T postgres pg_restore -U epure -d epure --clean --if-exists < epure-YYYYMMDD.dump
docker compose up -d
curl -sS http://localhost:8080/health

Expected

{"status":"ok"}

--clean drops objects before restore. Confirm /health and login. Keep EPURE_SESSION_SECURE matched to HTTP vs HTTPS.

What stays the same

No upgrade introduces Redis or a worker fleet.

  • Two-container topology (app + Postgres 16)
  • EPURE_MODE=all
  • RLS session variable: app.current_org_id
  • Volume names for Postgres and artifacts (unless you rename the Compose project)

Checklist

  • Dump taken and stored off-box
  • Artifacts archived if needed
  • docker compose … up -d completed without migrate errors
  • /health OK
  • Session still works
  • Ingest smoke test passed
What if migrate errors show in Epure logs?

Stop retrying a half-applied schema. Restore the dump, check out the previous commit, compose up. Migrations run when the epure process starts, so there is no separate migrate container. Keep the dump off-box: Troubleshooting.

Why does login loop after an upgrade?

Cookie Secure flag mismatch. The prod overlay sets EPURE_SESSION_SECURE=1; local HTTP needs 0. Keep that value matched to HTTP vs HTTPS when you roll back, then confirm /health. See Configuration.

Can I roll back the schema with an older binary?

Only if you restore the pre-upgrade dump first, since schema moves forward only. --clean on pg_restore drops objects before restore. Confirm /health and login after compose up.

Do I drain Redis or workers?

No. Two containers, with no Redis stream and no worker fleet. Stop epure, restore Postgres if needed, compose up. Artifacts live on the artifacts_data volume if you uploaded maps.