What's actually happening on deploy
When an orchestrator (Kubernetes, systemd, Docker, your PaaS) stops your app, it doesn't pull the plug. It sends SIGTERM and starts a countdown — typically 30 seconds. Two things can happen:
You handle it
You stop taking new requests, finish the ones in flight, close connections, and exit 0. No user notices the deploy.
You ignore it
The countdown expires, the platform sends SIGKILL (exit code 137), and any request still running is severed mid-flight.
An app that exits cleanly on SIGTERM shows exit code 143 (128 + 15). That's the goal.
What a graceful shutdown must do
Stop accepting new work
Close the listening socket so the load balancer stops routing new requests to this instance.
Drain in-flight requests
Let requests already being processed finish before you exit — don't cut them off.
Close connections & flush
End database pools, message-queue consumers, and flush logs/metrics so nothing is left half-written.
Have a timeout fallback
If draining hangs, force-exit before the platform's SIGKILL does it for you, so you control the outcome.
A correct handler (Node / Express)
const server = app.listen(process.env.PORT || 3000);
function shutdown(signal) {
console.log(`${signal} received — draining`);
server.close(async () => { // 1. stop accepting, wait for in-flight
await db.end(); // 2. close DB pool
await queue.close(); // 3. close consumers
process.exit(0); // 4. clean exit (143 overall)
});
// 5. fallback: don't hang forever
setTimeout(() => process.exit(1), 10_000).unref();
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT')); // Ctrl-C in dev
The same shape applies everywhere: trap the signal, stop intake, drain, close, exit — with a hard timeout so a stuck request can't keep the process alive past the grace period.
Why "I handle SIGTERM" still drops requests
- PID 1 in Docker doesn't forward signals. If your container starts the app via a shell (
sh -c "node ..."), the shell is PID 1 and may swallow SIGTERM. Use the exec form or an init liketini. - The event loop is blocked. A synchronous task means your handler never runs before SIGKILL arrives.
- Keep-alive connections aren't closed.
server.close()waits on idle keep-alive sockets; set akeepAliveTimeoutor close them explicitly. - No readiness flip. Mark the instance "not ready" first so the load balancer drains it before you stop the server.
Zero-drop deploys, handled by the agent
Infraveil can coordinate configured start/stop paths for managed services on your own servers. A configured service may be removed from traffic and given its configured drain period before it stops; actual request handling depends on the service, proxy, and deployment path. Managed deployments use the configured manual, exact-hash allowlist, or automatic approval mode; automatic mode removes a practical per-release veto. Recovery uses separately configured, bounded automation, and selected events may be reported subject to component, retention, pagination, configuration, and reachability.
Frequently asked questions
What is SIGTERM?
SIGTERM (signal 15) is the standard "terminate gracefully" request sent to a process on deploy, restart, or scale-down. Unlike SIGKILL, it can be caught and handled, giving your app a chance to shut down cleanly.
What's the difference between SIGTERM and SIGKILL?
SIGTERM politely asks the process to stop and can be handled. SIGKILL (signal 9) cannot be caught or ignored — the kernel kills the process immediately. Platforms send SIGTERM first, then SIGKILL if you don't exit in time.
Why do requests get dropped on deploy?
Because the app was stopped while still serving traffic. Either it didn't handle SIGTERM, or it didn't drain in-flight requests before exiting, so the platform's SIGKILL cut them off.
What is exit code 143?
143 = 128 + 15, meaning the process exited due to SIGTERM. Seeing 143 on shutdown is a good sign — it means your app responded to the signal rather than being force-killed (137).
Client-side, no signup — they run in your browser.