09. Disposability
1. The rule: processes you can start or stop at a moment’s notice
The factor is one sentence: maximize robustness with fast startup and graceful shutdown. Behind it is a single property. A twelve-factor process is disposable, meaning it can be started or stopped at a moment’s notice, and you earn that two ways: by making the process quick to start and clean to stop.
Disposability is what makes the previous factor safe. Concurrency handed the app’s lifecycle to a process manager whose recovery strategy is deliberately blunt: if a process dies, start another one. That’s only safe if a process really can die and be replaced at any instant without losing work or dragging the system down. Disposability is that guarantee, the flip side of the manager restarting things. The manager can be careless about killing processes precisely because they’re built to be killed. It’s also the precondition for everything elastic: fast scale-up, rapid deploys, and a fleet that heals itself all depend on processes you can add and drop freely. As the series introduction put it, a twelve-factor app scales by adding more copies of itself; disposability is what lets it add and drop them without ceremony.
2. Fast startup
A disposable process should go from launch command to ready in a few seconds, not minutes. Short startup buys agility: releasing new code is fast, since a deploy is mostly stopping old processes and starting new ones; scaling up is fast, since a load spike is answered by processes serving within seconds; and the process manager can relocate a process onto another machine cheaply, since moving it costs little more than the time to boot elsewhere.
Short startup is mostly a consequence of statelessness. A process that follows Processes holds no durable state of its own, so it has almost nothing to load at boot: no local cache to warm from disk, no session data to reconstruct, no prior state to replay. It connects to its backing services, reads config from the environment, binds its port and it’s ready. The less a process carries, the faster it starts.
3. Graceful shutdown on SIGTERM
Graceful shutdown is the other half, and it starts with a signal. When the process manager wants a process to stop, it sends SIGTERM. A disposable process treats that as a request to wind down cleanly, not an order to die on the spot: stop taking on new work, finish what’s already in hand, then exit on its own. What “finish what’s in hand” means depends on the process type, and the split is the same web versus worker one from the last factor.
3.1. Web processes: stop listening, drain in-flight, exit
For a web process, graceful shutdown is three steps: stop listening on the service port so no new requests arrive, let the requests already in flight run to completion, then exit. The process refuses new work but honors what it already accepted, so no client gets a connection killed mid-response.
This leans on an assumption: requests are short, a few seconds at most, so the in-flight set drains almost immediately. That holds for ordinary HTTP traffic and breaks for anything long-lived. Long-poll connections, streaming responses, and open websockets can stay open far longer than any shutdown wants to wait. Those follow a different pattern: the client reconnects when its connection drops, so the server can close them at shutdown and let clients re-establish against whichever process is still up.
3.2. Worker processes: return the job to the queue
For a worker process, graceful shutdown means giving back the job it was working on. A worker pulls a unit of work off a queue, processes it, and acknowledges it when done. If SIGTERM arrives mid-job, the graceful move is to return that job to the queue so another worker can pick it up, rather than acknowledging work that never actually finished. How you return it depends on the queue: on RabbitMQ you NACK the message (a negative acknowledgement that re-queues it); Beanstalkd returns a job to the ready queue automatically the moment the worker disconnects; a lock-based system releases the lock so the item becomes available again. The queue is a backing service, an attached resource the worker talks to over the network, so returning the job is just one more message to it.
This too rests on an assumption, and it’s the load-bearing one: jobs must be reentrant. Returning a half-finished job only helps if re-running it from the top is safe, which means the same job run twice must not double its effects. You get that two ways: wrap the job in a transaction, so a partial run rolls back and leaves nothing behind, or make the operation idempotent, so running it again converges on the same result. Charge a card once, not once per redelivery. Reentrancy is what turns “return the job and let someone re-run it” into a safe recovery rather than a way to duplicate work.
4. Robust against sudden death: crash-only design
Graceful shutdown is the polite path, and you can’t count on getting it. A machine can lose power, a kernel can panic, and a process manager that has waited long enough stops asking politely and sends SIGKILL, which the process cannot catch, handle, or defer. It just stops, mid-request or mid-job, with no chance to drain or hand anything back. A process that’s only safe to stop when it stops gracefully isn’t really disposable; it’s disposable on good days.
For the worker case, the cover is already in place, and it doesn’t need the worker to cooperate. A robust queueing backend returns a job to the queue whenever the client holding it disconnects or times out, cleanly or not. The worker doesn’t have to NACK on the way out; the queue notices the connection is gone and re-queues the job on it’s own. Sudden death becomes just another disconnect.
Generalize that and you get crash-only design: architect the process so that crashing is a normal, expected way for it to stop, and a clean shutdown is just a crash you saw coming. If every stop, graceful or violent, leaves the system recoverable and is recovered the same way, there’s no separate clean-shutdown path to get right and no failure a SIGKILL can expose that a power cut wouldn’t. The process is safe to stop at any instant, by any means.
5. Disposability is what makes Kubernetes work
Everything a modern platform does to keep your app healthy involves killing and restarting your processes on purpose, constantly. A rolling deploy kills old pods while starting new ones. The cluster autoscaler removes a node, and every pod on it, when demand drops. Spot-instance reclamation takes a whole machine back with little warning. Kubernetes assumes a pod can be terminated at any time, and its self-healing model depends on that being safe, which is to say on disposability.
The termination sequence is Factor 9 written as a protocol. When Kubernetes stops a pod it sends SIGTERM, waits a configurable terminationGracePeriodSeconds, and if the process is still alive when the timer runs out, sends SIGKILL. That window is the graceful-shutdown budget: drain inside it and you exit cleanly, overrun it and you’re killed. A preStop hook runs just before the SIGTERM to begin the app’s own drain.
The rest of the factor shows up just as literally. Fast startup is what a readiness probe rewards: Kubernetes withholds traffic from a pod until the probe passes, so a slow-starting process sits idle instead of taking load, and a fast one joins the fleet quickly. Load-balancer connection draining is the web-process drain from earlier, enforced from outside: when a pod goes terminating, the service stops sending it new connections while letting existing ones finish, which is “stop listening on the port” done at the load balancer instead of in the app. The platform mechanizes every part of disposability, and in return it gets to move your processes around freely.
6. The hard part: make every stop safe
Both halves of this factor are harder in practice than the rule sounds, and plenty of apps get neither. Many handle SIGTERM badly or not at all. A process running as PID 1 in a container is the classic trap: by default PID 1 ignores SIGTERM unless the app installs a handler, so the process sails through the grace period untouched and gets a SIGKILL at the end of it, dropping every in-flight request or job it held. That’s the PID-1 gotcha from the last factor, and it quietly turns “graceful shutdown” into “no shutdown handling at all.” The web-drain assumption strains too: “requests are short” fails for the long-lived connections from earlier, none of which finish inside a normal grace period.
Fast startup isn’t always in reach either. A JVM service pays for warmup and just-in-time compilation before it’s at full speed; a process loading a large machine-learning model into memory can take tens of seconds to be ready; heavy framework initialization adds its own tax. This is the same cost that shows up as serverless cold starts, the pause while a fresh execution context boots your code from nothing. When you can’t make startup fast, you mitigate: keep a pool of warm or minimum replicas so there’s always a ready process to take load, and pre-load expensive resources before traffic arrives. Make startup as fast as you can, and cover the part you can’t.
So the discipline that actually pays off isn’t “always shut down gracefully,” which you can’t guarantee. It’s the crash-only design from earlier paired with idempotent, reentrant jobs: make every stop safe, so it never matters whether a given stop was polite. Handle SIGTERM when you can and drain cleanly, but architect so that a process killed mid-flight loses nothing that isn’t recoverable and corrupts nothing at all, because the queue returns its job and re-running it is safe. Once that’s true, a platform can kill and restart your processes as freely as it likes, on a deploy, an autoscale, a lost spot node, and nothing breaks. That freedom is the whole self-healing, elastic model, and disposability is what buys it.