Safe OnlineExam
Deploy the application

Configuration reference

Every runtime setting — profiles and hardened validation, database, required application values, file-based secrets, Canvas/LTI endpoints, SEB options, and secret rotation.

The application reads its configuration from process environment variables (or mounted secret files). It does not load .env files itself — Compose loads an env file explicitly, and Cloud Run injects values. Protect any local production file with chmod 600 and keep it out of version control.

Profiles and validation

The active profile is the first non-empty value among APP_ENV, NODE_ENV, and the alias SPRING_PROFILES_ACTIVE:

Resolved valueResult
prod / productionProduction profile and hardened validation.
testTest profile; in-memory repositories.
Anything else, or unsetDevelopment profile.

Cloud Run is always hardened

Cloud Run is treated as hardened even for an isolated APP_ENV=dev service. Hardened validation requires real database, Canvas, LTI, OAuth, secret, URL, and certificate values, and startup fails before listening if configuration is unsafe or incomplete. That fail-fast is intentional — a misconfigured exam tool should not accept traffic.

PostgreSQL

PostgreSQL 17+ is the supported durable store. The app uses ordinary PostgreSQL protocol settings and is not tied to a managed provider.

VariableDefaultNotes
DATABASE_HOST127.0.0.1Host, IP, or Unix socket directory. Required when hardened.
DATABASE_PORT54321–65535. Stays 5432 for Cloud SQL sockets.
DATABASE_NAMEcanvas_sebDedicated application database. Required when hardened.
DATABASE_USERcanvas_sebApplication role. Required when hardened.
DATABASE_PASSWORDRequired when hardened. Prefer DATABASE_PASSWORD_FILE.
DATABASE_PASSWORD_FILEReadable file with the password. Conflicts with the direct variable.
DATABASE_SSL_MODEdisabledisable, require, verify-ca, verify-full. Verify over untrusted networks.
DATABASE_POOL_MAX5Per-process max, 1–100. Size the DB for pool max × instances/jobs.
DATABASE_CONNECTION_TIMEOUT_MS10000100–120000 ms.
DATABASE_STATEMENT_TIMEOUT_MS30000100–600000 ms.
DATABASE_CLEANUP_BATCH_SIZE5001–10000. Read by the cleanup command, not normal startup.

Cloud Run connects to Cloud SQL with DATABASE_HOST=/cloudsql/PROJECT:REGION:INSTANCE and DATABASE_SSL_MODE=disable (the authenticated socket is local to the sandbox). An external managed PostgreSQL should normally use verify-full with a trusted chain; for a private CA, mount the CA file and set NODE_EXTRA_CA_CERTS before startup.

Run npm run db:migrate (or the migration job/service) before a new revision, and npm run db:cleanup on a schedule. /ready fails until every checked-in migration is applied.

Required application values

VariablePurposeHardened requirement
NODE_ENVNode runtime mode.production for the deployed image.
APP_ENVApplication profile.dev (isolated non-prod) or prod.
PORTHTTP port.Defaults to 8080; platforms may inject it.
TOOL_URLPublic origin of this deployment.HTTPS origin only; no path/query/credentials.
CANVAS_DOMAINConnected Canvas origin.HTTPS origin only.
LTI_CLIENT_IDLTI 1.3 Developer Key client ID.Required.
LTI_PRIVATE_KEYRSA private JWK for tool signing.RSA 2048+ bits, exponent 65537, RS256.
LTI_DEPLOYMENT_ID_CHECKING_ENABLEDEnforce the deployment-ID allowlist.Defaults true; false only for controlled self-service.
LTI_DEPLOYMENT_IDInstalled External App deployment ID.Required when checking is enabled; comma/newline allowlist.
CANVAS_API_CLIENT_IDCanvas API OAuth client ID.Required and distinct from the LTI key.
CANVAS_API_CLIENT_SECRETCanvas API OAuth secret.Required secret.
SESSION_SECRETExpress session signing secret.≥32 chars, different from state encryption.
STATE_ENCRYPTION_KEYAES-GCM material for opaque LTI/OAuth state.≥32 chars, different from session signing.
SEB_CONFIG_ENCRYPTION_CERT_PEMPublic X.509 cert used to encrypt .seb.Valid end-entity RSA cert whose Key Usage permits encryption.

SEB_CONFIG_ENCRYPTION_CERT_PATH is the file alternative for the public certificate. The matching private key is never a server input.

Two keys, never mixed

LTI_CLIENT_ID (LTI 1.3 Developer Key) and CANVAS_API_CLIENT_ID (API OAuth Developer Key) come from different Canvas registrations. Mixing them breaks either signed launches or Canvas API authorization. Likewise, SESSION_SECRET and STATE_ENCRYPTION_KEY must be different values.

Deployment-ID policy

LTI_DEPLOYMENT_ID_CHECKING_ENABLED=true (default) restricts launches to the IDs in LTI_DEPLOYMENT_ID. Set it to false only when the configured Canvas issuer and LTI client ID are intentionally trusted to create course-level installs. Disabled mode still requires Canvas's signed deployment-ID claim and retains token signature, issuer, audience, nonce, target-link, and browser/state validation — it removes only the preconfigured allowlist.

File-based secrets

These values accept a mutually exclusive _FILE alternative, read once at startup:

  • DATABASE_PASSWORD_FILE
  • LTI_PRIVATE_KEY_FILE
  • CANVAS_API_CLIENT_SECRET_FILE
  • SESSION_SECRET_FILE
  • STATE_ENCRYPTION_KEY_FILE
  • SEB_QUIT_PASSWORD_FILE

Setting both a direct value and its _FILE form is rejected. Docker/Kubernetes secret mounts work without provider-specific SDKs. The checked-in compose.secrets.yaml override wires these file paths into the app, migration, and cleanup processes and supplies PostgreSQL its own POSTGRES_PASSWORD_FILE.

Canvas and LTI endpoints

Canvas cloud defaults are built in. Override the authorization and JWKS URLs for a Canvas with different LTI endpoints. LTI_ISSUER must equal the iss in that Canvas's launch — do not assume it equals CANVAS_DOMAIN.

VariableDefaultNotes
LTI_ISSUERhttps://canvas.instructure.comHTTPS issuer URL.
LTI_KEY_SET_URLhttps://sso.canvaslms.com/api/lti/security/jwksCanvas platform JWKS endpoint.
LTI_AUTH_URLhttps://sso.canvaslms.com/api/lti/authorize_redirectCanvas OIDC authorization endpoint.
CANVAS_API_BASE_URL${CANVAS_DOMAIN}/api/v1Must match the configured Canvas origin when hardened.
CANVAS_REDIRECT_URI${TOOL_URL}/api/oauth2callbackIf supplied, must equal the exact callback when hardened.

See Self-hosted Canvas for the override procedure and the sso.canvaslms.com failure it prevents.

SEB and diagnostics

VariableDefaultNotes
SEB_QUIT_PASSWORDOptional managed exit-password fallback; must pass password policy.
SEB_REQUIRED_DOMAINSemptyConcrete reviewed hostnames; wildcards and IdP hosts rejected.
SEB_CONFIG_ENCRYPTION_ENABLEDtrueMust stay true when hardened.
SEB_CONFIG_ENCRYPTION_CERT_PATHPublic certificate path (used by the Compose secret mount).
SEB_CONFIG_ENCRYPTION_PUBLIC_KEY_PEMLocal-dev fallback; insufficient when hardened.
SEB_CONFIG_ENCRYPTION_PUBLIC_KEY_PATHFile form of the local public-key fallback.
HOST0.0.0.0Bind address.
USE_IN_MEMORY_STOREfalseLocal/test only; hardened runtimes reject it.
APP_DEBUG_ENABLEDfalseHardened runtimes reject true.
APP_DETECTOR_DIAGNOSTICS_ENABLEDfalseSanitized detector detail; production rejects true.
APP_ASSET_VERSIONOptional client cache version; K_REVISION used when present.

Secret rotation

Rotate one secret at a time. Create a new immutable version, point the runtime at the numbered version, deploy, smoke test, then disable the old version.

Rotating…Consequence
SESSION_SECRETInvalidates active sessions.
STATE_ENCRYPTION_KEYInvalidates outstanding opaque state.
LTI signing materialRequires Canvas/JWKS coordination.
SEB certificateRequires distributing the new private identity and issuing fresh configurations.

For Cloud Run, always pin numbered Secret Manager versions — never latest. The exact secret names and bootstrap order are in Google Cloud. Rotation runbooks are in Operations.

Data model (for reference)

Runtime data lives in nine tables plus the schema_migrations ledger: admin_course_connections, admin_tool_presets, admin_tool_preset_assignments, assessments, courses, canvas_oauth_tokens, sessions, transient_states, and operation_locks. You do not manage these by hand — migrations own the schema and cleanup drains expired rows.

On this page