$ -weight: 500;">docker run --name myapp -d my-image:latest
# ... later ...
time -weight: 500;">docker -weight: 500;">stop myapp
# real 0m10.234s
-weight: 500;">docker run --name myapp -d my-image:latest
# ... later ...
time -weight: 500;">docker -weight: 500;">stop myapp
# real 0m10.234s
-weight: 500;">docker run --name myapp -d my-image:latest
# ... later ...
time -weight: 500;">docker -weight: 500;">stop myapp
# real 0m10.234s
# Inside a running container
ps -ef
# UID PID PPID CMD
# root 1 0 node server.js
# Inside a running container
ps -ef
# UID PID PPID CMD
# root 1 0 node server.js
# Inside a running container
ps -ef
# UID PID PPID CMD
# root 1 0 node server.js
// No SIGTERM handler
setInterval(() => console.log('alive'), 1000);
// No SIGTERM handler
setInterval(() => console.log('alive'), 1000);
// No SIGTERM handler
setInterval(() => console.log('alive'), 1000);
FROM node:20-alpine
COPY app.js /app.js
CMD ["node", "/app.js"]
FROM node:20-alpine
COPY app.js /app.js
CMD ["node", "/app.js"]
FROM node:20-alpine
COPY app.js /app.js
CMD ["node", "/app.js"]
-weight: 500;">docker build -t pid1-demo .
-weight: 500;">docker run --name demo -d pid1-demo
time -weight: 500;">docker -weight: 500;">stop demo
-weight: 500;">docker build -t pid1-demo .
-weight: 500;">docker run --name demo -d pid1-demo
time -weight: 500;">docker -weight: 500;">stop demo
-weight: 500;">docker build -t pid1-demo .
-weight: 500;">docker run --name demo -d pid1-demo
time -weight: 500;">docker -weight: 500;">stop demo
// With SIGTERM handler
process.on('SIGTERM', () => { console.log('shutting down cleanly'); process.exit(0);
});
setInterval(() => console.log('alive'), 1000);
// With SIGTERM handler
process.on('SIGTERM', () => { console.log('shutting down cleanly'); process.exit(0);
});
setInterval(() => console.log('alive'), 1000);
// With SIGTERM handler
process.on('SIGTERM', () => { console.log('shutting down cleanly'); process.exit(0);
});
setInterval(() => console.log('alive'), 1000);
const server = http.createServer(handler);
server.listen(3000); function shutdown() { console.log('SIGTERM received, draining...'); // Stop accepting new connections, finish existing ones server.close(() => process.exit(0)); // Hard -weight: 500;">stop if drain takes too long setTimeout(() => process.exit(1), 8000).unref();
} process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
const server = http.createServer(handler);
server.listen(3000); function shutdown() { console.log('SIGTERM received, draining...'); // Stop accepting new connections, finish existing ones server.close(() => process.exit(0)); // Hard -weight: 500;">stop if drain takes too long setTimeout(() => process.exit(1), 8000).unref();
} process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
const server = http.createServer(handler);
server.listen(3000); function shutdown() { console.log('SIGTERM received, draining...'); // Stop accepting new connections, finish existing ones server.close(() => process.exit(0)); // Hard -weight: 500;">stop if drain takes too long setTimeout(() => process.exit(1), 8000).unref();
} process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
import signal, sys
def shutdown(signum, frame): print('cleaning up') sys.exit(0)
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
import signal, sys
def shutdown(signum, frame): print('cleaning up') sys.exit(0)
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
import signal, sys
def shutdown(signum, frame): print('cleaning up') sys.exit(0)
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
-weight: 500;">docker run --init --name demo -d pid1-demo
-weight: 500;">docker run --init --name demo -d pid1-demo
-weight: 500;">docker run --init --name demo -d pid1-demo
FROM node:20-alpine
RUN -weight: 500;">apk add --no-cache tini
COPY app.js /app.js
# tini becomes PID 1 and execs your command as a child
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "/app.js"]
FROM node:20-alpine
RUN -weight: 500;">apk add --no-cache tini
COPY app.js /app.js
# tini becomes PID 1 and execs your command as a child
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "/app.js"]
FROM node:20-alpine
RUN -weight: 500;">apk add --no-cache tini
COPY app.js /app.js
# tini becomes PID 1 and execs your command as a child
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "/app.js"]
# Shell form — PID 1 is /bin/sh, NOT node
CMD node /app.js # Exec form — PID 1 is node
CMD ["node", "/app.js"]
# Shell form — PID 1 is /bin/sh, NOT node
CMD node /app.js # Exec form — PID 1 is node
CMD ["node", "/app.js"]
# Shell form — PID 1 is /bin/sh, NOT node
CMD node /app.js # Exec form — PID 1 is node
CMD ["node", "/app.js"]
CMD ["sh", "-c", "exec node /app.js"]
CMD ["sh", "-c", "exec node /app.js"]
CMD ["sh", "-c", "exec node /app.js"] - It does not get the default signal handlers. If you send SIGTERM to PID 1, and the process has no explicit handler for it, the signal is ignored. This is a kernel-level protection meant to keep init from being killed accidentally.
- It is responsible for reaping zombie child processes. When any process in the system has its parent die, it gets re-parented to PID 1. When those orphans eventually exit, PID 1 must call wait() on them or they become zombies forever. - Default to exec-form CMD and ENTRYPOINT. It's a one-line change that prevents an entire class of bugs.
- Add --init or bake in tini for any image where you don't fully control the application's signal handling.
- Test your shutdown path locally with time -weight: 500;">docker -weight: 500;">stop <container>. If it takes more than two or three seconds, something is wrong. Catch it before production does.
- Set sensible stopGracePeriodSeconds in Kubernetes to match your app's actual drain time. Don't just leave it at the 30-second default and hope.
- Log on SIGTERM receipt. When something goes wrong in production, you want to know whether the signal arrived at all or was silently dropped.