Skip to main content
Safe Online Exam upgrades follow a staged procedure on both supported platforms: a migration job runs first, a candidate revision is verified before it receives traffic, and rollback is always available without reversing database migrations. This page covers the pre-upgrade checklist, the migration requirement, platform-specific upgrade and rollback procedures, and the npm commands that underpin each step.

Pre-Upgrade Checklist

Before upgrading any environment, complete these steps:
1

Review the changelog and release notes

Read the CHANGELOG for the target version. Note whether the release adds a database migration, changes Canvas LTI registration URLs, adds OAuth scopes, or modifies public SEB compatibility endpoints — each requires additional post-upgrade steps.
2

Verify the image digest and attestation

Production must pin the immutable digest from the GitHub Release, never a mutable tag:
Verify the checksum of the downloaded bundle before extracting:
Then verify the image attestation using the exact repository, signer workflow, source commit, and source tag printed in the release notes.
3

Run acceptance on a test environment first

Apply the upgrade to a non-production or testbed environment and complete the relevant acceptance checks — including the Canvas and SEB acceptance sequence — before promoting to production.
4

Create a database backup

Both upgrade helpers create a backup automatically before any migration or traffic change. For Compose, also ensure the named postgres_data volume is intact. For Cloud Run, the upgrade waits for the Cloud SQL backup operation to report SUCCESSFUL before proceeding.

Migration Requirement

The migration job must complete successfully before the new application image receives any traffic. The /ready endpoint fails until all migrations have been applied. Do not manually route traffic to a new revision before its migration job has succeeded.
Every upgrade helper enforces this order automatically. If you are managing a custom container platform, run the migration as a one-shot pre-traffic job:
The equivalent npm script for local or source-based environments:
The cleanup job runs on a schedule to remove expired sessions, state, and other transient records:
Multiple application instances share all state through PostgreSQL. No sticky sessions are required. You can run any number of application replicas against the same database and they will remain consistent without any additional coordination.

Docker Compose Upgrade

1

Download and verify the new bundle

Download the next Compose release bundle and verify its checksum before extraction:
Read the extracted README.md before proceeding.
2

Preserve your existing environment and secrets

Keep the existing protected environment file, secret files, client identity record, and database volume in their durable installation directory. Merge any new template keys from the new bundle into your existing configuration — do not overwrite local configuration wholesale.
3

Run the upgrade helper with your existing environment path

Invoke the new bundle’s upgrade.sh with the path to the existing environment file. Relative secret and backup paths resolve from the directory containing that file:
The helper will:
  1. create a PostgreSQL custom-format backup and validate it;
  2. pull the exact pinned images;
  3. apply checked forward migrations;
  4. restart the topology; and
  5. verify readiness at /ready.
4

Copy the backup to off-host storage

Move the newly created backup to encrypted, access-controlled off-host storage immediately. The named postgres_data volume is not a substitute for a verified backup.
5

Run post-upgrade smoke checks

Verify the key endpoints after the upgrade completes:
Then complete the Canvas and SEB acceptance sequence for the roles affected by the release.

Compose Rollback

Application rollback does not undo database migrations. Restore an older image only after confirming it supports the current schema. Data rollback requires a reviewed restore into a controlled target:
  1. Stop the current topology.
  2. Confirm schema compatibility between the prior image and the migrated database schema.
  3. Update the image digest in your environment file to the prior pinned release digest.
  4. Restart the topology.
For data rollback, restore a backup into a controlled target first. Do not overwrite the active database as the first diagnostic action.

Cloud Run Upgrade

1

Download and verify the new Cloud Run bundle

2

Preserve existing environment and bootstrap records

Keep cloudrun.env, bootstrap state, client identity, and .state files in a durable installation directory outside source control. Merge new template keys from the new bundle instead of overwriting local configuration.
3

Run the upgrade helper

Invoke the new bundle’s upgrade.sh with the path to the existing environment file:
The helper will:
  1. validate the current environment and bootstrap contract, create any newly required numbered Secret Manager versions, and grant the existing runtime identity access;
  2. create an on-demand Cloud SQL backup, wait for its operation to finish, and require the resulting backup status to be SUCCESSFUL;
  3. run the new migration job;
  4. update the cleanup job and apply current environment and secret bindings;
  5. deploy a no-traffic candidate revision;
  6. temporarily enable a previously disabled generated URL so the candidate can be verified;
  7. verify candidate readiness and JWKS;
  8. cut traffic over explicitly; and
  9. verify the custom origin and restore the prior generated-URL policy.
4

Run post-upgrade smoke checks

After traffic cutover, verify:
Also inspect:
  • the active revision and exact image digest;
  • migration and cleanup job execution history;
  • Cloud Scheduler’s last execution;
  • Cloud SQL backup/PITR status, storage, and connections; and
  • numbered secret-version references.

Cloud Run Rollback

If a candidate check fails during upgrade.sh, the previous revision retains traffic automatically. Completed forward migrations remain in place. For an explicit rollback after traffic has been cut over:
  1. Confirm the prior revision supports the current database schema (application rollback never reverses migrations).
  2. Use the bundle’s explicit schema-compatibility confirmation before proceeding.
  3. Route traffic back to the prior revision using the bundle’s rollback helper or gcloud run services update-traffic.
  4. The rollback helper verifies TOOL_URL when configured and re-disables the generated URL if applicable.
For data recovery, restore a Cloud SQL backup into a controlled target first. Do not overwrite the active database as the first diagnostic action.

OAuth Token Encryption Migration

If upgrading from a version that stored OAuth tokens in plaintext (pre-1.1), follow the two-phase mode transition before enforcing encryption.
1

Deploy in compatibility mode first

Set OAUTH_TOKEN_ENCRYPTION_MODE=compat in .env.secrets and run the upgrade. The compatibility revision can read both plaintext and encrypted tokens while writing only encrypted rows.
2

Enforce and rewrite existing tokens

Change the mode to enforce, recreate the application, then run the one-shot rewrite:
Run the command a second time and require 0 updates before retiring the old plaintext key.
A pre-encryption application revision is not a valid rollback target after encrypted writes begin. The staged compat deployment is mandatory for any existing installation — the upgrade helper rejects an enforce request when the current serving revision has not yet reported compat or enforce.

npm Commands Reference

The following npm scripts are used during upgrades and maintenance. The built variants run the compiled output directly without requiring TypeScript tooling, matching the commands the deployment helpers invoke internally. For production deployments managed by the release bundles, the built variants invoke the compiled output directly without requiring TypeScript tooling. The migration and cleanup jobs call npm run db:migrate:built and npm run db:cleanup:built respectively, which resolve to node dist/server/server/data/migrate.js and node dist/server/server/data/cleanup.js --drain.