Engineering guide
Modern Twelve-Factor Apps: A Practical Cloud-Native Guide
Use the Twelve-Factor principles as operational design constraints, then add explicit reliability, security, and observability practices for distributed systems.
The Twelve-Factor App methodology remains a useful way to make services portable, repeatable, and easier to operate. It is not a Kubernetes checklist and it is not an architecture certification. Kubernetes can provide useful primitives, but a deployment can still have hidden configuration, sticky local state, unsafe release behavior, or opaque dependencies.
For DevOps and software engineers, the practical goal is simpler: build a service that can be configured per environment, started more than once, observed under load, and recovered without depending on one machine or one operator's memory. That design improves release safety and gives teams clearer options when latency rises, a region degrades, or traffic grows at the edge.
What the methodology is for
The original factors cover codebase, dependencies, configuration, backing services, build/release/run separation, processes, port binding, concurrency, disposability, development-production parity, logs, and administrative processes. They address recurring delivery failures:
- An application behaves differently because configuration is embedded in its build.
- A restart loses session or work state that other instances cannot recover.
- A deployment includes an unreviewed dependency or an irreversible schema change.
- Operators cannot connect a slow request to the dependency or release that caused it.
Treat the factors as questions to answer in design and review, not as reasons to add tooling. A stateless API, for example, may still perform poorly if every request synchronously crosses a distant region to a database. Portability does not remove network physics.
Kubernetes is an implementation platform, not a compliance badge
Kubernetes can schedule replicas, inject configuration, and restart failed containers. It does not automatically make an application Twelve-Factor. Application code and operational policies still determine state handling, dependency timeouts, migrations, logs, and graceful shutdown.
Apply the factors to a modern service
One codebase and explicit dependencies
Keep one version-controlled codebase for a deployable service. Build immutable artifacts from a locked dependency graph, record the source revision, and scan both application and base-image dependencies. Do not depend on a package already installed on a node, a mutable image tag, or a locally mounted source directory.
In a container workflow, build once and promote the same image digest through environments. A mutable latest tag prevents a release from being reproduced. Generate an SBOM where your supply-chain process requires one, and make dependency updates routine rather than an emergency-only activity.
Configuration belongs outside the artifact
Put deploy-specific values such as endpoints, feature flags, credentials, and timeouts in environment-specific configuration. Keep secrets in a dedicated secret system rather than source control or an image layer. Validate required configuration at startup, but do not log secret values when validation fails.
Configuration must also be bounded. An arbitrary environment variable that changes cache correctness or authorization without review is a production code path. Use typed configuration, defaults that are safe for the environment, ownership, and a change history. See Cloud-Native Configuration and Secrets for implementation details.
Backing services are attached resources
Databases, queues, object storage, identity providers, and third-party APIs should be addressed through configuration rather than hard-coded hostnames or local assumptions. This makes substitution possible, but it does not make every dependency interchangeable. Capture each service's consistency model, quotas, authentication, failure behavior, and latency budget.
Use connection pooling and bounded clients. A pool that is correct for one pod can exhaust a database when a horizontal autoscaler creates fifty pods. Budget connections at the fleet level, reserve capacity for migrations and recovery, and close idle clients during shutdown.
Build, release, and run are separate concerns
The build produces an artifact. A release combines that artifact with reviewed configuration and migration decisions. Run starts the release. Separate those steps so a production incident can be tied to an image digest, configuration version, and change record.
Avoid downloading application dependencies at container start and avoid editing running containers. Use a deployment controller, GitOps workflow, or another auditable mechanism to declare the intended release. Rollback plans must consider data compatibility: rolling back code after a destructive schema migration may be unsafe.
Design processes for replacement
Run service processes as disposable workers. Keep request session data in a shared, appropriately secured store or encode only integrity-protected state at the client when that is suitable. Do not use a pod filesystem, in-memory map, or a load balancer's affinity cookie as the only record of an important business action.
Disposability requires both fast startup and graceful termination:
- Handle
SIGTERMand stop accepting new work. - Keep the readiness endpoint false while draining.
- Finish or safely cancel in-flight work within the termination grace period.
- Extend queue visibility or hand work back before exit.
- Close database and HTTP connections cleanly.
Kubernetes readiness and liveness probes serve different purposes. A readiness probe controls whether a pod receives traffic. A liveness probe restarts a process considered stuck. Do not make a liveness probe fail merely because a shared database is temporarily unavailable: restarting every pod can amplify an external outage. A startup probe is often better for slow initialization.
Make latency and scaling explicit
Horizontal replicas improve throughput only when a constrained resource is not elsewhere. Before scaling, identify the limiting signal: CPU, memory, connection count, queue age, database saturation, or downstream latency. Autoscaling on CPU alone can add replicas during a slow dependency incident, increasing retries and connection pressure without restoring user requests.
Set a request deadline from the user-facing entry point and allocate smaller budgets to each downstream call. Propagate cancellation and a correlation or trace context. For an interactive path, cache safe responses close to users, keep dynamic calls near their data where possible, and avoid serial chains of remote calls. An edge cache can reduce origin work and global latency for cacheable content, but it cannot correct an unbounded origin dependency.
Useful service-level indicators include availability, successful request latency, saturation, queue delay, and dependency error rate. Instrument them with traces, metrics, and structured logs before an incident. The Google SRE workbook explains how to turn these signals into service-level objectives and error budgets.
Common failure modes
- Stateful sessions in a replica: a restart or scale event signs users out or loses transactions. Move durable state to a shared service and make operations idempotent.
- Environment variables treated as a vault: leaked deployment manifests, debug output, or broad read permissions expose credentials. Use least-privilege secret delivery and rotation.
- One readiness endpoint for everything: a transient dependency incident removes all capacity. Check whether this instance can serve the route, then use circuit breaking and degraded behavior for optional dependencies.
- Unlimited retries: retries increase traffic precisely when a dependency is overloaded. Set deadlines, retry only safe operations, use exponential backoff with jitter, and enforce a retry budget.
- Logs only in a container filesystem: evidence disappears at reschedule time. Emit structured events to a durable collection pipeline with trace and request identifiers.
A practical adoption sequence
Choose one critical service and map its startup configuration, durable state, dependencies, release path, latency budget, and shutdown behavior. Fix the most dangerous assumption first, usually hidden configuration, local state, or an unbounded dependency client. Then test a pod replacement, a dependency timeout, a rollback, and a traffic increase in a realistic environment.
Make the result part of the service's definition of done: a pinned build, documented configuration contract, probes, graceful shutdown, dependency budgets, telemetry, and a reversible release strategy. Revisit it when the service adds a region, queue, external provider, or edge-delivered route.
Authoritative references
- The Twelve-Factor App
- Kubernetes: Container lifecycle hooks and termination
- Kubernetes: Probes
- Google SRE Workbook
- OpenTelemetry documentation
Connect application design to delivery architecture
Talk to Optimi about resilient edge and origin delivery patterns that support predictable latency and failure isolation.
Discuss performance architecture