Skip to content

ADR 0003: Static container image with configuration rendered at start-up ​

  • Status: accepted
  • Date: 2026-10-20
  • Evidence: commits 63f2b3a (run-time config.js), 3baf85c (hardened nginx image); docker/Dockerfile, docker/entrypoint.sh, app/sidedoc-frontend/src/config/runtimeConfig.ts

Context ​

Deployment settings (Sentry, the pinned schema version, schema URLs, the Content Security Policy) were baked in at build time through VITE_* variables, so every deployment needed its own build. The CLARIAH software requirements ask that parameters that vary between deployments are applied at run time (15.3), that containers run without root, with a read-only file system, and log to stdout.

Decision ​

  • Build one image for all deployments: a multi-stage build where Node builds the backend (tsup) and the standalone editor (vite build --mode standalone), and the runtime stage is nginx-unprivileged (alpine, slim) serving the app, the schemas and the SKOS vocabularies.
  • The page loads ./config.js before the app bundle. It sets window.__SIDEDOC_CONFIG__, and readConfig() prefers those values over build-time ones. In the repository public/config.js is empty, so development builds behave as before.
  • docker/entrypoint.sh reads SIDEDOC_* environment variables (or *_FILE secrets), checks each one against a strict pattern, writes config.js and the nginx configuration to /tmp (the only writable path) and then starts nginx. An invalid value stops the container with exit code 64.
  • The CSP is sent by nginx as a header so operators can extend it at run time. Image builds set SIDEDOC_CSP_META=false to leave out the <meta> CSP that other builds include.

Consequences ​

  • The same image runs in every environment; configuration is documented in the deployment guide.
  • Configuration is validated before anything is served, so a typo fails loudly at start-up.
  • Run-time configuration is still public: config.js is served to every visitor.
  • Settings the image does not expose (for example the persistence providers of the demo app) remain build-time only.
  • The image contains committed schema copies, so it does not need the schemas repository to build.