openCBT

Releases and updates

How the image is built, and how a server moves between versions.

How a build happens

Every push to main runs .github/workflows/publish-image.yml:

  1. Checks — npm run typecheck and npm run lint. A failure publishes nothing.
  2. Build and push to ghcr.io/reelmza/opencbt, tagged with the full commit sha and latest. A v1.2.3 git tag also publishes 1.2.3 and 1.2.
  3. Prune — old versions are deleted, keeping the newest few, because private package storage on the free plan is 500 MB and an image is around 260 MB.

Layers are cached between runs, so a build that only changes application code takes a few minutes.

Tokens

Each server needs a read-only token to pull:

docker login ghcr.io -u YOUR-GITHUB-USERNAME   # paste a read:packages token

Use a fine-grained token limited to the opencbt package, one per box, so a school's server can be cut off on its own. GitHub does not accept account passwords for the registry.

Updating a server

cd ~/deploy                                  # or ~/schools/<name>
docker compose -f compose.onsite.yml pull
docker compose -f compose.onsite.yml up -d

The app applies any database migrations as it starts, then serves. Only the layers that changed are downloaded, so a code-only update is a few megabytes.

Never on an exam morning. A school's .env should pin OPENCBT_IMAGE to a version you have already run somewhere else:

OPENCBT_IMAGE="ghcr.io/reelmza/opencbt:sha-9f3c1d2..."

Rolling back

Set OPENCBT_IMAGE to the previous tag and bring it up again. The limit is the database: a release that migrated the schema cannot be undone by running the old image, because the old code does not know the new shape. Two habits keep this cheap:

  • take a backup before updating a school's server;
  • read the release's commits for a migration before deciding to update mid-term.

Offline installs

A school with no usable internet can be fed the image by hand:

# on a machine that has it
docker pull ghcr.io/reelmza/opencbt:latest
docker save ghcr.io/reelmza/opencbt:latest | gzip > opencbt.tar.gz

# on the school's server, from a flash drive
gunzip -c opencbt.tar.gz | docker load

Then set OPENCBT_IMAGE to that tag in the school's .env. No docker login is needed on that box at all.

What is in the image

The app and the worker, the built Next.js app, the production dependencies, Prisma's migration tooling and the PostgreSQL 16 client (for the backup button). About 260 MB to download, 1.3 GB unpacked. No credentials: every secret lives in the .env beside the compose file.

The worker

Both processes run the same image; the worker is the same image started with worker. It builds exports and takes backups, so a deployment without it shows results it cannot hand over. Every compose file here already includes it.

On this page