要让 Docker 容器在 docker stop 时执行自定义清理脚本,核心是确保 PID 1 进程能捕获 SIGTERM 并主动调用清理逻辑;Docker 固定发送 SIGTERM(10 秒后发 SIGKILL),推荐用 trap + exec 的 entrypoint.sh 或官方推荐的 tini 工具实现信号转发与清理触发。
要让 docker 容器在 docker stop 时执行自定义清理脚本,核心不是“配置停止信号”,而是确保 pid 1 进程能捕获 sigterm 并主动触发你的清理逻辑。docker 固定发送 sigterm(不可改),关键在于容器内如何响应它。
容器内必须由 PID 1 处理 SIGTERM
Docker 停止流程是固定的:
→ 发 SIGTERM 给 PID 1
→ 等待默认 10 秒
→ 超时未退出则发 SIGKILL(不可捕获、不可拦截)
所以,你的清理脚本能否运行,完全取决于 PID 1 是否:
- 能接收
SIGTERM - 不直接退出或忽略
- 主动调用你的清理逻辑,再自行退出
很多默认启动方式(如 sh -c "myapp & wait"、tail -f /dev/null、sleep infinity)会让 shell 占据 PID 1,但它不转发信号给子进程,也不响应 SIGTERM —— 清理脚本根本不会执行。
推荐两种可靠实现方式
✅ 方式一:用 trap + exec 编写 entrypoint.sh(轻量、无依赖)
#!/bin/sh
# /entrypoint.sh
# 定义清理函数(可独立成文件,也可内联)
cleanup() {
echo "[INFO] Starting graceful shutdown..."
/usr/local/bin/cleanup.sh # 你自己的深度清理脚本
}
# 捕获 TERM 和 INT 信号
trap cleanup TERM INT
# 启动主应用,并保持为 PID 1(关键!)
exec "$@"
Dockerfile 中使用:
COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] CMD ["your-app", "--config", "/etc/app.conf"]
⚠️ 注意:必须用
exec "$@",不能用$@或sh -c "$@",否则主应用不是 PID 1,信号无法直达。
✅ 方式二:用 tini 作为 init 进程(Docker 官方推荐)
tini 是专为容器设计的轻量级 init,自动转发信号、防止僵尸进程,还支持预停止钩子(pre-stop hooks)。
Dockerfile 示例:
# 使用带 tini 的基础镜像(如 `phusion/baseimage`),或手动安装 FROM alpine:latest RUN apk add --no-cache tini COPY cleanup.sh /usr/local/bin/cleanup.sh RUN chmod +x /usr/local/bin/cleanup.sh # 设置 tini 为入口点,并指定 pre-stop hook ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"] CMD ["your-app"]
/entrypoint.sh 可简化为:
#!/bin/sh # tini 已负责信号转发,你只需确保主进程可响应 # cleanup.sh 可通过 tini 的 pre-stop hook 或 trap 触发 trap "/usr/local/bin/cleanup.sh" TERM exec "$@"
✅ 优势:
tini内置--pre-stop支持(需 v0.19+),可直接注册清理命令,无需自己写 trap。
自定义清理脚本(cleanup.sh)要点
#!/bin/sh # /usr/local/bin/cleanup.sh set -e # 任一命令失败即退出 echo "$(date): begin cleanup" # 示例操作(按需调整): pkill -f "worker.py" 2>/dev/null || true redis-cli -h redis flushall 2>/dev/null || true rm -rf /tmp/upload_* sync curl -s -X POST http://notify/api/shutdown?cid=$HOSTNAME --timeout 3 || true echo "$(date): cleanup completed"
- 避免阻塞型操作(如无超时的
curl、长 sleep),否则可能超 10 秒被SIGKILL - 不要依赖
/proc/1/cmdline或ps判断父进程 —— 容器中不可靠 - 日志写入 stdout/stderr,可用
docker logs查看;也可写标记文件用于验证(如touch /tmp/cleanup.done)
验证是否生效(三步实测)
-
手动发信号测试
docker run -d --name test-app your-image docker kill -s TERM test-app docker logs test-app | grep "cleanup"
-
延长超时再 stop(留足时间)
docker stop --time=30 test-app
-
检查标记文件是否存在(最可靠)
docker cp test-app:/tmp/cleanup.done . 2>/dev/null && echo "✅ 清理已执行"
不复杂但容易忽略:
真正起作用的不是“配置信号”,而是让 PID 1 成为你可控的、带 trap 或 tini 的启动层。只要这层存在且正确 exec,你的业务清理就能在 docker stop 时稳定触发。











