Cloud-Native & 12-Factor Principles
Config in the environment (one image everywhere), stateless disposable processes (Redis session, S3 files, graceful SIGTERM), backing services attached by URL, and immutable build/release/run separation, all so a process is safe to kill and restart anywhere at any time.
The factors are a design lens
The 12-factor methodology and cloud-native principles are a checklist for building an app that a platform can run, replace, and scale automatically. In an interview they are a design lens: when asked to make a service container-ready, walk the factors and name the specific change for each, rather than saying "make it cloud-native" as a vibe. The four that carry most of the weight:
Config in the environment
Config and secrets live outside the image, in env vars, a ConfigMap, or a secrets manager. The payoff is one immutable artifact promoted unchanged from dev to staging to prod (dev/prod parity). The moment you bake an environment-specific config file into the image, you need a different build per environment, and parity is gone. A baked-in database URL or API key is the classic anti-pattern.
Stateless, disposable processes
A process must hold no state that another instance would need. No in-memory session that only lives on one box, no user files written to local disk. Move session to Redis, files to object storage (S3). Then any instance can serve any request, and the platform can start a new instance or kill an old one at any moment. "Disposable" also means fast startup and graceful shutdown: on SIGTERM the process stops taking new work, drains in-flight requests, and exits, so a scale-down or node drain loses nothing.
Backing services as attached resources
Databases, caches, queues, and blob stores are attached by URL and credentials, not compiled in. A local Postgres and a managed Aurora are the same "attached resource" to the app, so you can swap one for the other by changing config, with no code change. This is what makes an instance truly interchangeable across environments.
Build, release, run separation, and immutable infrastructure
Build produces an image, release binds that image to a config to make a versioned, immutable release, and run executes it. You never mutate a running box; to change anything you build a new image and replace instances. This is what makes rollback trivial (re-run the previous release) and eliminates config drift.
Design for failure
In a cloud-native world instances vanish routinely: spot reclamation, autoscale scale-in, node drains, zone loss. So health checks, retries, and graceful shutdown are required, not optional, and logs must stream to stdout as an event stream for the platform to collect (never written to a local file that dies with the instance).
Interview nuance: the highest-signal move is to walk a specific legacy service through the checklist and name the concrete change per factor: "session is in local memory -> move to Redis; uploads go to local disk -> move to S3; config is a baked-in app.conf -> move to env vars." That specificity is what separates a strong answer from reciting the factor names.
Recap: config in the environment (one image everywhere), stateless disposable processes (Redis session, S3 files, graceful SIGTERM), backing services attached by URL, and immutable build/release/run separation, all so a process is safe to kill and restart anywhere at any time.
Apply
Your turn
The task this lesson builds to.
Explain how you would apply the 12-factor and cloud-native principles to make a legacy stateful service ready for containers and autoscaling, calling out config, state, backing services, and disposability.
Think about
- What makes a process safe to kill and restart anywhere at any time?
- Where should configuration and secrets live so one image runs in every environment?
- How do you treat databases, caches, and queues so instances stay interchangeable?
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Design the concrete migration to make a 12-year-old Java monolith running a company's core billing container-ready and autoscalable without a billing outage, prioritizing which factors to fix first. It writes invoices to a local /data directory, keeps user sessions in the JVM heap, reads a 400-line config.properties baked into the WAR, and is deployed by hand to two pet servers.
Think about
- Why is externalizing state the priority-one blocker?
- How do you sequence the fixes by risk rather than all at once?
- How does idempotency prevent double-billing once elasticity is on?