12-Factor App Compliance
Snackbox 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 Snackbox goal of being easy to deploy and operate at scale.
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 (systemd, Docker) |
| 2 | Dependencies | ✅ | All dependencies declared in go.mod + go.sum; go mod download in CI; static binary bundles everything at build time - including SQL migrations embedded via //go:embed |
| 3 | Config | ✅ | All runtime configuration via environment variables (internal/config); optional .env file as a developer convenience, never baked into the binary |
| 4 | Backing services | ⚠️ | SQLite is embedded - not an attachable network resource. Media files are also local-only. Single-host deployments are unaffected; horizontal scaling and Kubernetes require the planned MariaDB + S3 backends (2.0.0) |
| 5 | Build, release, run | ✅ | Multi-stage Dockerfile cleanly separates build and runtime. CI has distinct build → release stages. The binary is immutable once built |
| 6 | Processes | ⚠️ | Access tokens are validated per-request with no in-process session state. Refresh tokens are persisted in SQLite (the backing service), which is correct 12-factor behavior. The rate-limiter holds per-IP token-bucket state in memory; this state is lost on restart and differs across replicas |
| 7 | Port binding | ✅ | Two self-contained HTTP servers: the public API on configurable LISTEN_ADDR (default :8080) and the Prometheus metrics server on METRICS_ADDR (default :9091); no external app server required at runtime |
| 8 | Concurrency | ⚠️ | Go's goroutine-per-request model handles concurrent reads well. Write concurrency is bounded by SQLite's single-writer lock; horizontal scaling past one instance is not practical without the MariaDB backend (2.0.0) |
| 9 | Disposability | ✅ | SIGTERM and SIGINT both trigger graceful shutdown: in-flight requests drain within SHUTDOWN_TIMEOUT (default 15 s), then the process exits cleanly. SIGHUP is trapped and treated as a no-op with a warning - configuration reload is not supported; restart the service to apply changes |
| 10 | Dev/prod parity | ✅ | Same binary and Docker image in all environments; .env.example keeps local config close to production defaults; no dev-only dependencies or code paths |
| 11 | Logs | ✅ | All output written to stderr via log/slog; no log files or rotation. JSON format (configurable) is compatible with log aggregation tools. Aggregation is left to the operator (journald, Docker log driver, Loki, etc.) |
| 12 | Admin processes | ✅ | Dedicated one-off subcommands ship in the same binary as the server: migrate (apply pending schema migrations), create-admin (create an admin user), backup (snapshot database + media), publish-due (transition scheduled content), seed-fixtures (load demo content). All subcommands load config from the same environment variables as the server. Migrations also run automatically at startup for operator convenience |
Summary
Fully compliant (9 / 12): Codebase, Dependencies, Config, Build/release/run, Port binding, Disposability, Dev/prod parity, Logs, Admin processes.
Partially compliant (3 / 12):
| Gap | Resolution |
|---|---|
| Backing services (IV) | MariaDB + S3 media backends planned for 2.0.0 |
| Processes (VI) | In-memory rate-limiter state; acceptable for single-instance, breaks with replicas |
| Concurrency (VIII) | Resolves together with MariaDB backend (2.0.0) |