Skip to main content

Upgrading and rollback

An upgrade is a tag bump plus a deploy. Images are built only from tags, so VIDRA_CORE_TAG, VIDRA_USER_TAG and VIDRA_SEARCH_TAG in the env file name exactly what runs.

Upgrade

# On a git checkout: compose files and Caddyfile only. CHECKOUT TREES ONLY.
cd /opt/vidra && git pull --ff-only
$EDITOR env/production.env # VIDRA_CORE_TAG=v0.5.0 …
./deploy/deploy.sh # dump → pull → gated migrate → up → probe

On a bundle tree there is no git pull. The upgrade is the tag bump plus vidra deploy. To take a release's new compose files and deploy scripts as well, unpack its bundle over the tree — it contains no env/ secrets and no Caddyfile.local, so neither is touched:

cd /opt/vidra
curl -fsSLO https://github.com/yegamble/vidra-core/releases/download/v0.5.0/vidra-bundle_v0.5.0.tar.gz
tar -xzf vidra-bundle_v0.5.0.tar.gz # overwrites tracked files, keeps yours
$EDITOR env/production.env # VIDRA_CORE_TAG=v0.5.0 …
./deploy/deploy.sh

Verify its checksum against the release's SHA256SUMS first if you did not get it through install.sh.

Release the three repos at the same version. Nothing enforces it, but rollback.sh v0.5.0 sets all three tags from one argument, promotion copies three identical lines, and "which build is running?" during an incident has one answer instead of three. Release a component that did not change anyway — skipping it means its tag does not exist.

Rollback

# App only, no schema change:
./deploy/rollback.sh v0.4.0 # or: vidra rollback v0.4.0

# Across an incompatible schema change:
./deploy/compose.sh stop api frontend
./deploy/restore.sh backups/pre-deploy-<ts>.dump.gz
./deploy/rollback.sh v0.4.0

rollback.sh rewrites the three tag values, pulls, restarts and re-probes. It does not touch the database, and that is only safe because of the one-release schema-compatibility policy: release N−1's code must run against release N's schema. That policy is mechanically enforced by migrate-lint.sh and the schema-compat workflow — see Production deployment.

A true schema rollback is "restore the pre-deploy dump", not "migrate down". That is what the second form above does, and it is why deploy.sh refuses to proceed when its pre-deploy dump fails.

There is no auto-rollback

Failed health probes leave the broken release running. deploy.sh reports it and stops; deciding to roll back is yours.

The embedded-migrator tag floor

Both migration one-shots are the service image itself, running its compiled-in migrate up. deploy.sh and rollback.sh carry a MIN_EMBEDDED_MIGRATE_TAG constant and refuse a VIDRA_CORE_TAG or VIDRA_SEARCH_TAG below it.

The reason is a hang, not an error: an older image's main() ignores the migrate up argv and starts an API server, so the one-shot never exits. deploy.sh would hang mid-pipeline and rollback.sh inside up -d. A hang with no message is worse than a refusal.

To run a component release older than the floor you must also check out the meta-repo revision that shipped with it — the pre-embedded compose files drove a separate migrator container with a bind-mounted migrations/ directory. VIDRA_USER_TAG is not gated: the frontend has no migrator.

Raise the floor, never lower it.

Cutting a release

make release VERSION=v0.5.0                    # all three repos; prompts first
make release VERSION=v0.5.0 CONFIRM=1 # same, unattended
make release VERSION=v0.5.0 REPOS="vidra-core" # one repo only
./deploy/release.sh --yes v0.5.0 # the script directly
vidra release --yes v0.5.0 # the CLI, which execs that script

Each component repo's publish-container.yml runs on release: published and pushes ghcr.io/<owner>/<repo>:<tag> — so cutting the release is building the image. release.sh creates the release in each repo, watches the resulting workflow run to its conclusion, then verifies the image is really in GHCR. It exits non-zero with a per-image summary if any repo fails, and it does not deploy anything.

The meta-repo is tagged too — first, and without a release — because vidra-core's release-assets.yml builds the deployment bundle by checking this repo out at the same tag. Run release.sh from a clean main; it refuses to tag a HEAD that is not already on origin/main.

Guards, all of which fire before the first release is created:

  • the tag must match ^v[0-9]+\.[0-9]+\.[0-9]+$ — the leading v is what every image reference assumes;
  • gh must be authenticated;
  • the tag must not already exist in any target repo;
  • HEAD must be an ancestor of origin/main, and an existing meta tag of that name must point at that very commit;
  • vidra-user needs the NEXT_PUBLIC_API_BASE_URL repository variable set, or its workflow refuses to build and there is no published image for any tag:
    gh variable set NEXT_PUBLIC_API_BASE_URL -R yegamble/vidra-user -b https://your.origin

Re-publishing an existing tag — a build that failed on a transient registry error — needs no new release. Each publish-container.yml takes a workflow_dispatch with a required tag input and refuses a tag that does not exist, so a typo can never publish the default branch:

gh workflow run publish-container.yml -R yegamble/vidra-search -f tag=v0.5.0

Merge order, when the compose revision changes

The chain is bootstrap.sh → component checkouts → docker-compose.yml include:s vidra-core's compose file. A host on a new compose revision runs against whatever images the tags name, so component releases land before the compose revision that requires them:

  1. vidra-core — release the change.
  2. vidra-search — same.
  3. the meta-repo revision, with any floors raised to the tags from 1 and 2, then VIDRA_*_TAG bumped in the env file.