Skip to main content
Safe Online Exam’s Docker Compose release bundle provides a complete self-hosted installation path for Linux servers with Docker Engine and the Compose v2 plugin. This page covers downloading and verifying the release bundle, running the guided installer, configuring TLS, scheduling backups and cleanup, and upgrading to a new release using the bundle’s upgrade helper.
The application binds to 127.0.0.1:8080 by default. Canvas requires a stable public HTTPS origin, so you must place a TLS reverse proxy (Caddy, nginx, Traefik, or a managed ingress) in front of the application before creating any Canvas registrations. The bundle includes an optional Caddy profile for servers where ports 80 and 443 are available.

What the Bundle Contains

The Compose release bundle includes:
  • The exact application image digest for the release.
  • PostgreSQL 17 with a named postgres_data volume.
  • A one-shot migration service that gates application startup.
  • An opt-in cleanup service for expired sessions and one-time state.
  • Mounted file secrets (no plaintext values in compose.yaml).
  • Protected LTI and SEB identity bootstrap via setup.sh.
  • A backup and upgrade helper (upgrade.sh).
  • An optional Caddy HTTPS profile.

Host Requirements

Use a supported Linux distribution with current security updates and the following:
  • Docker Engine with the Compose v2 plugin installed.
  • Stable DNS, encrypted storage, and time synchronization.
  • Monitored disk capacity — the PostgreSQL volume grows with usage.
  • SSH access restricted to administrative networks.
  • Port 5432 not published to the public internet.

Installation

1

Download and verify the release bundle

Replace X.Y.Z with the target release version. Verify the checksum before extraction.
Read the extracted README.md before proceeding. It contains release-specific notes that take precedence over this general guide.
2

Run the guided installer

The setup.sh script creates a protected .env.secrets file, a secrets/ directory with mounted file secrets, and the client-only SEB identity. It prompts for required values through no-echo input — do not pass secrets as command-line arguments.
For a manual source-topology install, protect the environment file instead:
Prefer the bundle’s generated file-secret topology (secrets/ directory with mounted files) over placing production secret values directly in the environment file.
3

Configure your TOOL_URL and Canvas redirect URI

Before adding Canvas registrations, set the public HTTPS origin in your protected environment file. These values must be final before you create any Canvas Developer Keys.
If you are using the optional bundled Caddy profile, also set PUBLIC_HOST to the same hostname and ensure ports 80 and 443 are available on the host.
4

Configure and start the application

Start the full topology with your environment file and both compose files:
The migration service runs first and gates application startup. Check that it completed successfully before proceeding:
5

Configure TLS (optional Caddy profile)

If you are using the bundled Caddy profile, activate it alongside the base compose files. Caddy automatically obtains and renews a TLS certificate using ACME (Let’s Encrypt or ZeroSSL) once PUBLIC_HOST is set and ports 80/443 are accessible.For any other reverse proxy (nginx, Traefik, or a managed ingress), terminate TLS externally and proxy https://<your-domain> to 127.0.0.1:8080. Keep the application and PostgreSQL ports private.
6

Register the tool in Canvas

With the service running and reachable at its public HTTPS origin, proceed to the Canvas Setup guide to create the API OAuth Developer Key and the LTI 1.3 Developer Key. Return here to deploy the final service revision with LTI_CLIENT_ID and LTI_DEPLOYMENT_ID after Canvas registration.

Scheduling Cleanup

Run the maintenance profile at least once daily. Use a systemd timer, cron job, or your host’s scheduler:
Use absolute paths in unattended schedulers, rotate the scheduler’s logs, and alert when executions stop.

Backups and Restore Drill

The named postgres_data volume provides data durability across container restarts but is not a substitute for a database backup. Use pg_dump inside the PostgreSQL container to create a compressed, custom-format backup:
The dump is compressed but not encrypted. Encrypt it immediately with your institution’s approved tool, move it to access-controlled off-host storage, and remove the plaintext copy from the local filesystem.
Perform a restore drill into a separate database or isolated host on a regular schedule:
  1. Validate the archive with pg_restore --list.
  2. Restore with pg_restore --exit-on-error.
  3. Run the intended image’s migrations against the restored database.
  4. Inspect all application tables.
  5. Exercise an isolated LTI/assessment flow.
  6. Record recovery time and the newest restored row.
A backup that has never been restored is not verified.

Upgrade Procedure

1

Download and verify the new bundle

Download the next release bundle and verify its checksum following the same steps as the initial install. Do not overwrite your existing installation directory.
2

Run the upgrade helper with the existing environment path

Keep your protected environment file, secret files, client identity record, and database volume in a durable installation directory. Invoke the new bundle’s upgrade.sh with the path to the existing environment file:
The helper:
  • Creates a PostgreSQL custom-format backup and validates it.
  • Pulls the exact pinned images from the new bundle.
  • Applies forward migrations.
  • Restarts the topology with the new image.
  • Verifies readiness before reporting success.
Relative secret and backup paths resolve from the directory containing the existing environment file.
3

Copy the backup to encrypted off-host storage

The upgrade helper creates a pre-upgrade backup in the installation directory. Copy it to encrypted, access-controlled off-host storage before continuing.
4

Verify the upgrade

Confirm the application is healthy and that the new revision is serving traffic:
Then complete a real Canvas and SEB acceptance test before the next high-stakes assessment period.
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.

OAuth Token Encryption Upgrade (Existing Installations)

If upgrading from a version that used plaintext OAuth tokens, use the staged transition:
  1. Upgrade with OAUTH_TOKEN_ENCRYPTION_MODE=compat in .env.secrets. The helper generates the new secrets/oauth_token_encryption_keyring file if absent and never changes an existing keyring.
  2. After the compat revision is healthy, change the mode to enforce and recreate the application container.
  3. Run the token rewrite job:
Run the job a second time and confirm 0 updates before removing any retired key. Fresh installations start directly in enforce mode.

Next Steps

After the service is running and healthy, proceed to Canvas Setup to register the LTI and OAuth Developer Keys, then provision the SEB encryption identity following the Certificate Management guide.