Twelve-Factor Apps Today: Containers, Serverless and Stateful Workloads
Apply Twelve-Factor principles without confusing stateless application processes with a stateless platform. Covers secrets, serverless, durable work and safe migrations.
Use Twelve-Factor as an application contract
The Twelve-Factor methodology describes practices for building software-as-a-service applications that can be deployed and operated consistently. It remains a useful review framework. It is not a complete security model, a database operations manual or a requirement to adopt Kubernetes.
Apply it by asking what the application assumes about its environment. Can another instance start from the same declared inputs? Where does durable state live? What happens when the process disappears? Those questions work for containers and functions, even when the platform exposes different deployment and networking mechanisms.
This guide distinguishes the original factors from implementation choices for current platforms. It focuses on application behavior and operational boundaries, not a claim that every workload should follow every factor literally.
Map the factors to observable behavior
Use this compact checklist to begin the review, then investigate the boundaries below.
| Factors | Question for the application | |---|---| | Codebase and dependencies | Can a known revision and declared dependencies reproduce the candidate? | | Config and backing services | Can deploy-specific settings and service bindings change without editing application code? | | Build, release, run | Can you identify the artifact and configuration behind a running instance? | | Processes and concurrency | Can instances be added or replaced without losing authoritative state? | | Port binding and disposability | How does the platform invoke the application, and how does work survive termination? | | Dev/prod parity and logs | Are important dependencies comparable, and can operators retrieve diagnostic events outside the process? | | Admin processes | Do operational tasks use the intended release and controlled environment? |
This is a review aid, not a compliance score. Document an intentional exception when the workload needs a different contract.
Stateless processes do not imply a stateless system
The original process factor separates disposable application execution from persistent backing services. Local memory and disk can hold temporary work, but another process should not need that local state to continue authoritative work.
That does not prohibit databases, durable queues or files. It places them behind an explicit durability boundary. A useful application test is to terminate one instance and ask which accepted work, sessions or records become unrecoverable.
Kubernetes also supports stateful applications. StatefulSets provide stable identity and persistent-storage associations for workloads that need them. They do not, by themselves, implement a database's replication protocol, backups or correct recovery.
| State placement | Responsibility that remains | |---|---| | Managed database or queue | Choose service settings, access controls, retention and recovery objectives; test application behavior and restoration. | | Database operated on Kubernetes | Own database replication, storage behavior, upgrades, backup integrity and recovery as well as the cluster. | | Disposable application storage | Ensure loss is acceptable, or reconstruct the work from a durable record without unsafe duplicate effects. |
A managed service can reduce the infrastructure work your team operates. It does not transfer responsibility for the application's data model or prove that a restore will meet business needs.
Choose state placement from recovery requirements, operational capability and workload behavior. Do not move a database merely to make the application diagram look stateless.
Separate configuration from credentials handling
The original configuration factor recommends environment variables for deploy-specific configuration. The important design boundary is that changing a deployment's resource bindings does not require changing its source code.
A current implementation may use environment variables for non-secret settings and a protected file, secret provider or workload identity for credentials. That is an adaptation, not a claim that the original text prescribed every modern mechanism.
Choose a delivery method by asking:
- Which process identity can read the value?
- Can it appear in diagnostic output, environment dumps or crash reports?
- Does the application notice rotation, or does it need a controlled restart?
- Can access be revoked without rebuilding the image?
- What happens if the credential source is temporarily unavailable?
A Kubernetes Secret object is not automatically encrypted merely because its manifest uses an encoded value. The Kubernetes Secrets documentation warns that storage is unencrypted by default unless encryption at rest is configured, and recommends restricted access. Confirm the effective behavior of the specific cluster or managed service.
Keep secret values out of source control, container layers, test fixtures and ordinary logs. Record references or versions in release evidence where appropriate, without copying the values.
Containers and serverless expose different runtime contracts
An HTTP container can listen on a port provided by deployment configuration. An event-driven function may instead receive events through a platform handler, so literal application-managed port binding is not applicable.
Both still need declared dependencies, an identifiable release and a clear state boundary. Neither requires a service mesh. In Kubernetes, Service and Pod DNS provide discovery mechanisms; a mesh is an additional architectural choice for needs such as traffic policy or workload communication controls.
Serverless also does not mean every invocation starts in a new environment. AWS documents Lambda execution-environment reuse, including reuse of SDK clients and temporary caches. Treat that reuse as an optimization, not a durable storage guarantee, and do not retain sensitive request data for another invocation to encounter.
For event-driven processing, determine the actual source's delivery and retry contract. AWS warns that event-source mappings can deliver an event more than once. A handler needs an idempotency strategy appropriate to its effects, not an assumption that a successful function invocation makes duplicates impossible.
Limit concurrency according to downstream capacity and the platform's limits. Automatic scaling is useful only while the database, external API and budget can support the resulting work.
Worked example: a report exporter that survives replacement
Consider a hypothetical application that accepts a report request, queues work and writes a downloadable file. The API and worker may run in containers or functions.
Give the request a durable job identity. Store its authorized owner, requested inputs, state and output reference in a database. Arrange reliable dispatch from that record, for example with a transactional outbox or a reconciler, so a crash between saving the request and publishing a message does not strand accepted work.
The worker uses local disk only for disposable scratch space. It writes the final output to durable object storage and records the accepted object reference when the job completes. The design must tolerate both duplicate messages and overlapping workers.
One approach uses an expiring claim with a monotonically increasing fencing token. Claim only unfinished, eligible jobs. A completion update succeeds only if the job is still in progress, its claim token matches and its lease is valid, all checked atomically in the database. A stale worker may finish computing, but it cannot complete a newer attempt or replace a completed result. Write outputs to attempt-specific object keys; only the winning database update selects the authoritative object.
| Failure point | Required behavior | |---|---| | After accepting the request, before queue publication | Dispatch reconciliation finds the durable pending job and publishes it. | | After a worker claim, before producing output | The claim eventually becomes eligible for recovery; a replacement uses a new token. | | After object upload, before the completion update | Retry or reconciliation resolves the attempt without exposing an uncommitted output as complete. | | After completion, before message acknowledgment | Redelivery finds the completed job and does not publish a second authoritative result. | | An expired worker resumes late | Its stale token cannot replace the accepted output reference. |
This is a design sketch, not ready-to-run coordination code. Transaction isolation, lease expiry, conditional updates and cleanup must be implemented and tested for the selected database and queue.
Garbage collection can remove abandoned attempt objects only after checking that they are not referenced by a completed job or an active attempt. For external effects such as sending a notification, add a separate deduplication or reconciliation mechanism. A database completion flag cannot atomically undo an email already sent.
The application process remains disposable because recovery starts from durable state. The system is deliberately stateful.
Run migrations as coordinated release work
The admin-process factor treats one-off tasks as part of the application's release environment. It does not imply that every application replica should independently change the database during startup.
Putting schema migrations in an init container can cause concurrent attempts as Pods are created, and a long migration can block the rollout. For changes that require serialization, use an explicitly coordinated release task with a migration ledger, database-level locking or equivalent concurrency control, and a recoverable procedure.
A Kubernetes Job is a useful execution vehicle, not an exactly-once guarantee. The Job documentation notes that even a single-completion, single-parallelism Job can sometimes start the same program twice. The migration mechanism must safely handle re-entry or detect that intervention is required.
Prefer compatible expansion before changing readers and writers, then remove old structures only after their consumers are retired. Application startup can check schema compatibility without trying to perform unrestricted DDL. See enterprise database migration strategies for the cutover and recovery decisions.
Make disposability an exercised property
A graceful termination handler should stop taking new work, finish or relinquish bounded in-flight work, and leave durable state that another instance can interpret. It cannot guarantee completion before a forced termination. Test process loss in the middle of work, not only an orderly shutdown.
For containers, distinguish startup, readiness and liveness. Kubernetes probe semantics assign different consequences to them: startup checks initialization, readiness controls traffic eligibility, and liveness can cause restarts.
A database outage should not automatically trigger liveness failures across every application replica. Restarting healthy processes does not repair the database and can add load. Design readiness and degraded behavior around what that application can actually serve, including the consequence of making all replicas unready.
Logs should survive the process and avoid sensitive payloads. Add metrics for outcomes and backlog, plus traces where requests cross boundaries. Environment parity should preserve important versions and behavior without copying production personal data into development.
Next action: test the contract before changing platforms
For one workload, write down:
- The authoritative location of every kind of state.
- The artifact, configuration and credential mechanism needed to start a replacement.
- The outcome of a duplicate event and a forced termination.
- The migration owner and the old/new version compatibility window.
- The restore procedure and evidence that restored data is usable.
- The signal that reveals incomplete or incorrect work.
These decisions help whether the application stays on virtual machines, moves to containers or adopts functions. A cloud architecture review can use that worksheet to compare options against recovery and operating requirements, rather than treating platform adoption as the outcome.
The tradeoff is explicit: making processes easier to replace requires more discipline around durable state, idempotency, compatibility and recovery. Do not adopt a platform abstraction when the team cannot operate those contracts or when the workload’s state and latency constraints make the added indirection unjustified.
Primary references were checked in September 2026. The report exporter is an illustrative design, not an Ampity implementation claim.