ADR 0003: Static container image with configuration rendered at start-up
- Status: accepted
- Date: 2026-10-20
- Evidence: commits
63f2b3a(run-timeconfig.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 isnginx-unprivileged(alpine, slim) serving the app, the schemas and the SKOS vocabularies. - The page loads
./config.jsbefore the app bundle. It setswindow.__SIDEDOC_CONFIG__, andreadConfig()prefers those values over build-time ones. In the repositorypublic/config.jsis empty, so development builds behave as before. docker/entrypoint.shreadsSIDEDOC_*environment variables (or*_FILEsecrets), checks each one against a strict pattern, writesconfig.jsand 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=falseto 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.jsis 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.