确保容器内PID 1进程能直接响应SIGTERM:用CMD ["node", "server.js"]避免shell封装;应用代码需监听SIGTERM并执行清理;通过--stop-timeout延长停止宽限期;搭配--init使用tini处理信号与僵尸进程。
要让 docker 容器启动命令真正支持优雅退出,关键不是“加什么参数启动”,而是**确保容器内 pid 1 进程能直接收到并响应 sigterm**。信号能否传到应用层,取决于启动方式、进程结构和应用自身逻辑三者配合。
启动命令必须让应用成为 PID 1
常见错误是用 shell 封装启动,比如:
-
CMD node server.js→ 实际是/bin/sh -c "node server.js",shell 占据 PID 1,SIGTERM 发给 shell,但 shell 默认不转发给子进程 -
CMD sh -c "exec node server.js"→ 虽有 exec,但写法不规范,仍可能失效
正确做法是使用 exec 格式(JSON 数组),跳过 shell 层:
-
CMD ["node", "server.js"]✔️ 应用直接作为 PID 1 启动,可收 SIGTERM - 若需环境变量展开或简单逻辑,可用
CMD exec node server.js,但前提是基础镜像中/bin/sh支持 exec 替换自身
应用代码里必须监听 SIGTERM
即使进程是 PID 1,不写处理逻辑也只会被默认终止(无清理)。主流语言示例:
-
Node.js:
process.on('SIGTERM', () => { server.close(); process.exit(0); }) -
Go:
signal.Notify(c, syscall.SIGTERM); <-c; server.Shutdown(ctx) -
Python:
signal.signal(signal.SIGTERM, lambda s, f: cleanup() or exit(0))
注意:避免在 handler 中调用阻塞操作(如未设超时的 HTTP 请求),否则会拖慢退出流程。
启动时指定停止宽限期
Docker 默认只等 10 秒,对数据库刷盘、长连接 draining 或批量任务来说常不够。可在启动命令中显式延长:
docker run -d --stop-timeout=45 myapp- 搭配
--init参数更稳妥:docker run -d --init --stop-timeout=45 myapp(自动注入 tini,解决僵尸进程和信号转发问题)
该设置不会影响启动行为,只约束 docker stop 时的等待上限。
验证是否生效
停完容器后别只看“Exited”,要查两处:
-
docker ps -a→ 看退出码:Exited (0)表示正常退出;Exited (137)是被 SIGKILL 强杀(大概率超时) -
docker logs <container> | tail -10→ 搜索SIGTERM、shutting down、graceful等关键词,确认日志中有清理动作输出


















