Skip to content

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)