Safe Online Exam
Deploy the application

Docker Compose on Linux

Install or upgrade a verified Safe Online Exam release bundle on a Linux host with PostgreSQL, file secrets, optional Caddy TLS, cleanup, and backup safeguards.

The Compose release bundle runs the published application image, PostgreSQL 17, a one-shot migration service, and an opt-in cleanup service. It does not contain application source or any private SEB client identity material.

Use this path on a current, maintained Linux host with Docker Engine, Docker Compose v2, stable DNS, encrypted storage, time synchronization, monitored disk capacity, and restricted administrative access. Do not publish PostgreSQL. Canvas requires a stable public HTTPS origin.

Download and verify the release

export VERSION="X.Y.Z"
curl -fLO "https://github.com/JSB2010/safe-online-exam/releases/download/v${VERSION}/safe-online-exam-${VERSION}-compose.tar.gz"
curl -fLO "https://github.com/JSB2010/safe-online-exam/releases/download/v${VERSION}/safe-online-exam-${VERSION}-compose.tar.gz.sha256"
sha256sum --check "safe-online-exam-${VERSION}-compose.tar.gz.sha256"
tar -xzf "safe-online-exam-${VERSION}-compose.tar.gz"
cd "safe-online-exam-${VERSION}"

Read the extracted README.md, verify the published image attestation using the release-note command, and keep the exact bundled ghcr.io/jsb2010/safe-online-exam@sha256:... reference. Never replace it with a moving tag for production.

Guided installation

Run:

./setup.sh

The walkthrough creates and protects .env.secrets, asks whether bundled Caddy or an existing reverse proxy owns HTTPS, collects the public URL and Canvas values, generates database/session/LTI/certificate material, accepts the Canvas API secret without terminal echo, validates the rendered Compose model, and starts only after confirmation.

For automation, use the bundle's explicit non-interactive contract. Pass sensitive inputs through protected files, never command-line values:

./setup.sh \
  --non-interactive \
  --bootstrap \
  --no-caddy \
  --env-file .env.secrets \
  --canvas-api-client-secret-file /secure/input/canvas-api-secret

--configure-only validates the complete model without pulling or starting containers. Use --caddy only after setting PUBLIC_HOST and opening ports 80 and 443.

First-install Canvas bootstrap

Canvas cannot supply the LTI client and deployment IDs until it can read the running service configuration. On the first guided run, enter bootstrap-pending for both LTI values, but use the real final Canvas origin, public HTTPS URL, and API OAuth Developer Key.

After the stack and HTTPS are working:

  1. Create the LTI Developer Key using ${TOOL_URL}/lti/config.
  2. Install the external app and record its client and deployment IDs.
  3. Run ./setup.sh again and enter the real LTI values.
  4. Confirm /ready, load the Canvas theme loader, and complete role-based acceptance.

bootstrap-pending is a registration bootstrap value only. Signed LTI launches cannot work until the real values are saved and the app is recreated.

Runtime shape and HTTPS

The bundle starts PostgreSQL with a persistent postgres_data volume, a one-shot migrate service, the app, and an opt-in cleanup service. The application binds to loopback by default. Use an approved reverse proxy and set:

TOOL_URL=https://safe-online-exam.example.edu
CANVAS_REDIRECT_URI=https://safe-online-exam.example.edu/api/oauth2callback

The optional Caddy profile terminates public HTTPS when PUBLIC_HOST is set. Keep the app port and PostgreSQL private. Do not use a production HTTP TOOL_URL.

Cleanup and backups

Run cleanup at least daily from a monitored systemd timer or cron job:

docker compose \
  --env-file .env.secrets \
  -f compose.yaml \
  -f compose.secrets.yaml \
  --profile maintenance run --rm cleanup

Back up the database before high-risk changes and on the institution's documented schedule. A custom pg_dump is compressed but not encrypted; encrypt it immediately, move it to approved off-host storage, and remove the plaintext copy. Restore drills belong in a separate database or isolated host. Validate the archive, restore with --exit-on-error, run the intended migrations, inspect the application tables, and exercise an isolated flow. A backup that has never been restored is not verified.

Upgrade and rollback

Download and checksum the next bundle. Preserve .env.secrets, secrets/, the client identity record, and the database volume; merge new template keys. Then:

./upgrade.sh .env.secrets

The helper creates and validates a PostgreSQL custom-format backup, pulls the exact pinned images, applies forward migrations, restarts the topology, and checks readiness. Copy the backup to encrypted off-host storage.

Application rollback does not reverse database migrations. Only restore an older image after confirming it supports the current schema. Recover data by restoring into a controlled target; never make automatic down-migration the response to a failed upgrade.

Complete the acceptance sequence after installation or any meaningful upgrade.

On this page