Twelve-Factor Apps: Durable Principles & Modern Limits
The twelve factors are best understood as a system of deployment boundaries—source, artifact, config, release, process, state, and platform—not as a complete cloud-native checklist or a demand to use one particular tool.
The Twelve-Factor App is a 2011-era methodology, last updated in 2017, for software delivered as a service. Its lasting value is not that every application must obey twelve slogans. It is that a deployable service should have explicit boundaries between source, build artifact, deploy configuration, release identity, running processes, durable state, and the platform.
Those boundaries remove hidden inputs. If dependencies leak from a server, a rebuild is not reproducible. If configuration is baked into an image, the same artifact cannot move safely between environments. If a process owns durable state, replacement and horizontal scaling become data-loss operations. If an administrator runs a migration with a different checkout or dependency set, the database can move ahead of the application that owns it.
The durable model is a chain of identities and contracts. Configuration selects a release without mutating the artifact; the platform supervises runtime processes; durable state and telemetry cross explicit boundaries.
The mechanism behind the checklist #
The factors form a causal system:
- A traceable source revision and exact dependencies make a build reproducible.
- The build produces an immutable artifact. In an OCI workflow, a digest identifies its exact manifest, configuration, and layers.
- Deploy-specific configuration selects how that artifact will run. Artifact digest plus configuration revision and rollout policy identify a release.
- The platform launches one or more processes from that release, routes traffic, supplies configuration, supervises lifecycle, and captures output.
- Replaceable processes keep durable data in backing services. Their restart, reschedule, or duplication must therefore preserve correctness.
- The same release runs normal services, workers, and one-off administrative commands, so operational actions do not invent a second software supply path.
Break any link and a later property becomes unreliable. Mutable tags break release identity. Runtime package installation breaks artifact reproducibility. Local sessions break process replacement. A hand-written production script breaks parity even if the web process itself is perfectly packaged.
The twelve factors, translated #
| Factor | Durable constraint | Modern reading | Failure it prevents |
|---|---|---|---|
| I. Codebase | Every deployable is traceable to version-controlled source. | One repository is not required. A monorepo can produce many independently identified artifacts; a deployable may also be assembled from several repositories if provenance records every input. | Unknown source, divergent copies, and changes that cannot be reproduced. |
| II. Dependencies | Declare and isolate every runtime dependency. | Use lockfiles, hermetic or controlled builds, vendored tools where necessary, and a software bill of materials. “It exists on this host” is not a dependency specification. | Works-on-one-machine behavior and undeclared host coupling. |
| III. Config | Keep deploy-varying values outside source and immutable artifacts. | Environment variables are one transport, not the rule itself. Mounted files, configuration services, and secret stores are valid when ownership, validation, reload, and failure behavior are explicit. | Rebuilds per environment, leaked credentials, and untraceable variation. |
| IV. Backing services | Address external services through explicit resource bindings. | A locator can change without a code edit only when the substitute preserves the required transactions, ordering, consistency, latency, and failure semantics. | Provider details spreading through business code and resources that cannot be rebound. |
| V. Build, release, run | Build once; name releases; do not mutate a running artifact. | Promote an artifact by digest, attach provenance, and treat configuration or policy changes as new releases even when the bytes are unchanged. | Production-only builds, mutable deployments, and rollbacks to an unknown state. |
| VI. Processes | Compute processes do not own durable session or business state. | Local memory and disk may be caches or scratch space. Correctness must survive restart, relocation, retry, and concurrent replicas. Stateful workloads remain valid but need an explicit state lifecycle. | Sticky routing, data loss on replacement, and scale-out that changes behavior. |
| VII. Port binding | A service exposes a clear network contract to its environment. | The process usually listens on an assigned interface and port; a proxy, service mesh, or ingress can terminate external protocols without becoming part of application construction. | Hidden coupling to a particular host web server or deployment topology. |
| VIII. Concurrency | Separate workload types and scale them deliberately. | Service replicas, queue workers, scheduled work, and run-to-completion Jobs have different scaling and retry contracts. Internal threads or async tasks do not remove process-level resource and failure boundaries. | Scaling the wrong bottleneck and mixing unrelated lifecycle policies. |
| IX. Disposability | Start predictably and stop safely. | Handle termination signals, stop accepting work, drain in-flight operations within a grace period, and make retried jobs idempotent. Startup speed does not replace readiness. | Dropped work, duplicate side effects, and failed rollouts during replacement. |
| X. Dev/prod parity | Exercise production contracts before production. | Parity means compatible versions, protocols, schemas, security policy, and failure behavior. Laptop and production topology need not be identical. | Adapters hiding semantic differences and tests proving the wrong environment. |
| XI. Logs | Applications emit events; the platform routes and stores them. | Standard streams are a useful transport. Production observability also needs structured fields, timestamps, severity, resource identity, trace correlation, retention, access control, and alerting. | Per-host log files, lost events after replacement, and uncorrelated text streams. |
| XII. Admin processes | One-off work uses the same artifact, config contract, and dependencies as the service. | Run migrations, repairs, and consoles as audited Jobs with explicit identity, timeout, retry, concurrency, and rollback rules. | Snowflake shells and maintenance code that does not match the running release. |
Artifact, config, and release are different identities #
Let artifact A be identified by an immutable digest, and let deploy configuration have revision C. A release record can be modeled as
R = (artifact_digest A, config_revision C, rollout_policy P)
This is more precise than “version 42” pointing at a mutable image tag. If the artifact changes, there is a new release. If a feature flag, database endpoint, or resource policy changes, there is also a new release even when A does not. A rollback must select a previously known tuple; it must not rebuild old source and hope to obtain the same bytes.
SLSA provenance answers a related but different question: where, when, and how was A produced, and from which inputs? Release records make deployment selection traceable; provenance makes the artifact's production traceable. Neither substitutes for the other.
Environment variables have a specific lifecycle cost: a process receives a snapshot when it starts. Updating the source object does not rewrite that process environment; a restart or explicit reload mechanism is required. Mounted configuration can update, but the application still needs a defined observation, validation, and failure policy. “Externalized config” therefore describes ownership and mutability boundaries, not one syntax.
Stateless compute does not mean a stateless system #
A useful replacement test is:
If any one service replica disappears between two instructions, can another replica continue without missing durable information or applying an externally visible effect twice?
Passing that test requires more than moving a session to Redis. Workers may receive the same job again after a timeout. HTTP clients may retry after losing a response. A graceful termination can exceed its deadline and become an abrupt kill. Transactions, idempotency keys, deduplication records, leases, and resumable workflows are the mechanisms that make disposability true.
Stateful workloads are not twelve-factor failures. A database, durable queue, or replicated log deliberately owns state and may require stable network identity and storage. Kubernetes expresses that with different workload controllers: Deployments for interchangeable replicas, StatefulSets for stable identity and storage, and Jobs for run-to-completion work. The architectural rule is to distinguish those contracts instead of pretending every process is interchangeable.
The platform contract is wider than a port #
Port binding gives the router a stable way to reach a process, but orchestration needs several independent signals:
- a startup probe says initialization has completed enough for other probes to begin;
- a readiness probe says the instance should currently receive traffic;
- a liveness probe says restarting the instance is more likely to help than letting it continue;
- a termination signal plus grace period gives the instance a chance to become unready, drain, and exit;
- resource requests let the scheduler place work, while limits constrain consumption and can cause throttling or out-of-memory termination.
These signals are not interchangeable. A dependency outage may make a service temporarily unready while the process remains healthy. Using the same dependency check as liveness can make every replica restart during the outage, amplifying the incident the probe was supposed to detect.
Similarly, emitting to stdout solves collection at the process boundary, not diagnosis. Kubernetes explicitly treats a separate cluster-level logging backend as necessary for durable analysis. OpenTelemetry's log data model adds the resource and trace context required to connect an event to a service instance and request. Metrics, traces, profiles, alert rules, retention, and privacy controls still need their own design.
Worked deployment trace #
Suppose commit 8d6e… builds image digest sha256:ab42…. CI records its dependencies, test result, and provenance. Production configuration revision cfg-107 names a PostgreSQL endpoint, a queue, a feature policy, and references secret versions without embedding their values. The deployment controller records:
release-204 = (sha256:ab42…, cfg-107, rolling-25-percent)
The sequence is then traceable:
- A migration Job starts from
sha256:ab42…withcfg-107, acquires a migration lock, and exits successfully. - New service replicas start from the same digest. Startup remains false while local initialization runs; readiness becomes true only after the instance can serve its contract.
- The router sends traffic only to ready replicas. Structured events carry release, resource, trace, and span identifiers to the platform collector.
- During replacement, an old replica becomes unready, receives termination, stops accepting new work, completes or safely abandons in-flight work, and exits before the grace deadline.
- A worker killed after committing an external effect receives the job again. Its idempotency record turns the retry into a no-op instead of a duplicate charge.
If error rate rises, rollback selects the previous recorded release tuple. It does not rebuild an old branch. Database compatibility must already support the overlap between old and new application versions; twelve-factor names the release boundary but does not solve schema evolution.
Where strict readings fail #
- One codebase is too literal. Repository topology is an organizational choice. What matters is a complete mapping from deployable artifact to reviewed source and build inputs.
- Environment variables are not universally superior. They are awkward for large structured values, rotation, binary material, dynamic updates, and least-privilege projection. Preserve the separation, choose the transport deliberately.
- Dev/prod parity is contract parity. Reproducing production scale on a laptop is impossible and unnecessary. Reproduce protocol versions, schemas, security boundaries, and failure semantics in tests and staging.
- Horizontal scaling is not automatic. A stateless handler can still contend on one database, violate rate limits, reorder messages, or create a retry storm. Capacity and concurrency control remain system properties.
- Twelve factors are not a security or reliability certification. The original list omits identity, authorization, encryption, supply-chain integrity, health signaling, resource management, data lifecycle, and recovery objectives.
Review checklist #
- Can every running instance be mapped to an immutable artifact digest and source provenance?
- Are all dependencies declared, versioned, and available without relying on a prepared host?
- Which values vary by deploy, who owns them, how are they validated, and how do updates reach processes?
- Which processes are replaceable, which components own durable state, and what happens on retry or abrupt death?
- Are readiness, liveness, startup, termination, and resource contracts distinct and tested?
- Can backing services be rebound only within a verified semantic contract?
- Do logs carry enough structured context to join them with metrics and traces after a replica disappears?
- Do migrations and repairs use the exact release artifact, with auditable identity and concurrency controls?
- Can rollback select a known release without rebuilding, and are data/schema changes compatible with it?
- Which production requirements lie outside the twelve factors, and where are they specified?
The useful question is not “is this app twelve-factor?” It is “which hidden deployment inputs and lifecycle assumptions still prevent us from reproducing, replacing, scaling, observing, or rolling back it safely?”
Sources
- docsThe Twelve-Factor Apppdfnotes
- docsOCI Image Format Specification
- docsSLSA v1.2 — Provenance
- docsKubernetes — Workload Management
- docsKubernetes — ConfigMaps
- docsKubernetes — Good Practices for Secrets
- docsKubernetes — Pod Lifecycle
- docsKubernetes — Probes
- docsKubernetes — Resource Management for Pods and Containers
- docsKubernetes — Logging Architecture
- docsOpenTelemetry — Logs Data Model