12-Factor App Compliance
Plate aims to be fully compliant with the 12-factor app methodology. The 12-factor principles guide the design towards portable, self-contained, and operationally simple services — which aligns directly with the Plate goal of being easy to deploy and operate.
This document records the current compliance state and known gaps.
Evaluation
| # | Factor | Status | Notes |
|---|---|---|---|
| 1 | Codebase | ✅ | Single Git repository on GitLab; one codebase deployed to multiple targets (Docker, systemd) |
| 2 | Dependencies | ✅ | All dependencies declared in package.json and pinned via package-lock.json; npm ci in both Dockerfile stages ensures exact versions. Minor: --legacy-peer-deps is a temporary workaround for a TypeScript 6 upstream conflict |
| 3 | Config | ✅ | All runtime configuration via environment variables (src/config.ts); validateConfig() rejects invalid config at startup with a full error list; configs/.env.example documents every variable. dotenv is loaded unconditionally but never overwrites existing env vars — real environment variables always win, the .env file only fills gaps, and defaults in config.ts cover anything still unset |
| 4 | Backing services | ✅ | Snackbox is treated as an attached resource addressed entirely by SNACKBOX_API_URL; swapping instances requires only an env var change |
| 5 | Build, release, run | ✅ | Multi-stage Dockerfile separates build (TypeScript compilation) and runtime (compiled dist/ only); no compilation at runtime; npm start runs the pre-built output |
| 6 | Processes | ✅ | Stateless per-request handling; no session state or disk writes. The in-memory SimpleCache is process-local — each instance holds its own cache, which is correct 12-factor behavior; cache misses result in Snackbox API calls, not stale data |
| 7 | Port binding | ✅ | Two self-contained HTTP servers: main app on PORT (default 3000) and Prometheus metrics on METRICS_ADDRESS (default localhost:9091); no external app server required |
| 8 | Concurrency | ✅ | Scales horizontally by running multiple container instances behind a load balancer; no shared state between instances; container-level replication is the recommended scaling mechanism |
| 9 | Disposability | ✅ | SIGTERM and SIGINT trigger graceful shutdown: idle connections are dropped immediately on both servers, active connections drain within SHUTDOWN_TIMEOUT (default 10 s), then closeAllConnections() forces closure on both. process.exit(0) is called only after both the main server and the metrics server have confirmed closure. Docker HEALTHCHECK enables orchestrator-level health routing |
| 10 | Dev/prod parity | ✅ | Same Node.js 24-alpine base image in all environments; "engines" field enforces minimum version; Docker Compose provides a one-command local stack matching production topology; LOG_FORMAT switches automatically between pretty (dev) and json (prod) |
| 11 | Logs | ✅ | All output written to stdout via Pino; no log files or rotation in the application. JSON format in production is compatible with log aggregation tooling. Aggregation is left to the operator (Docker log driver, journald, Loki, etc.) |
| 12 | Admin processes | ✅ | One-off CLI scripts ship alongside the application and load config from the same environment variables: npm run theme:validate validates a theme directory, npm run generate:types regenerates types from the Snackbox OpenAPI spec. Plate is read-only — no database migrations or write operations |
Summary
Fully compliant (12 / 12): all factors pass.
Minor deviations (non-blocking):
| Deviation | Factor | Impact |
|---|---|---|
| Per-process in-memory cache | VI — Processes | Expected behavior; operators running multiple instances should size CACHE_MAX_ENTRIES and TTLs to account for independent caches |