1. 为什么“一键停所有容器”不是偷懒,而是专业习惯的起点

在本地开发环境里,我每天至少要启动、调试、重启五六个容器:一个 PostgreSQL、两个 Node.js API 服务、一个 Redis 缓存、一个 Nginx 网关,外加一个临时跑数据迁移的 Python 工具容器。最开始我也是一条命令一条命令地敲 docker stop api-v2 docker stop db docker stop cache ……直到某天下午三点,我正赶着提交 PR,手一滑把 docker stop api-v2 敲成了 docker stop api-v1 ——而 api-v1 正在跑一个关键的集成测试,结果测试断连、日志中断、CI 流水线直接报红。排查了 47 分钟才发现是自己误停了容器。那天之后,我删掉了所有单容器 stop 命令的历史记录,转而只用一套可复现、可验证、带安全缓冲的批量停止机制。

这不是为了图快,而是因为 Docker 的容器生命周期管理,本质上是一场与信号、时序、状态和资源释放的精密协作。你敲下 docker stop 的那一刻,不是在发一个“关机指令”,而是在向操作系统发起一次 受控的进程协商请求 :请主进程优雅退出、请数据库刷写事务日志、请 HTTP 服务器拒绝新连接并等待旧连接完成、请缓存层同步脏数据到磁盘……这个过程有默认的 10 秒窗口,但窗口长短、信号类型、是否强制终止,全取决于你是否理解底层逻辑,并主动干预。

所以,“停止所有容器”这件事,表面看是 CLI 技巧,内核却是 DevOps 工程师对系统行为边界的清晰认知。它解决的从来不是“多按几次回车”的效率问题,而是 避免状态不一致、防止资源泄漏、规避数据损坏、保障本地环境可重现性 这四类高频事故。适合谁?适合所有每天和容器打交道的人——前端开发者调联调环境时需要秒级重置;后端工程师做数据库迁移前必须确保无残留连接;SRE 在故障复现时需要干净的基线;甚至实习生第一次拉下项目代码,也需要一条不会出错的初始化/清理命令。关键词就三个: 信号(SIGTERM/SIGKILL)、过滤(filter)、可审计(auditability) 。接下来我会带你从内核信号讲起,一层层拆解每种方法背后的取舍,告诉你哪条命令该在什么场景下用,以及——更重要的是——哪条命令你永远不该在生产配置里抄来就用。

2. 容器终止的本质:信号、时序与应用层的契约

2.1 Docker 是怎么“请”容器退出的?不是命令,是谈判

很多人以为 docker stop 就像物理关机按钮,按下就断电。完全错误。Docker 的停止机制,本质是 Unix 进程间通信的一次标准实践:它不杀进程,它 发信号 。更准确地说,它向容器内 PID 1 的主进程发送一个软件中断信号(signal),由该进程自行决定如何响应。这个设计哲学源于 Linux 内核的进程管理模型——信号是操作系统提供给用户态程序的、最轻量也最可控的异步通知机制。

我们来看真实执行链路:

  1. 你运行 docker stop my-web-app
  2. Docker Daemon 接收到请求,查找容器对应的 containerd-shim 进程,再通过 runc 找到该容器的 PID 1(比如 node server.js java -jar app.jar
  3. Daemon 向该 PID 1 进程发送 SIGTERM (信号编号 15)
  4. 应用进程捕获 SIGTERM ,执行预设的 shutdown handler:关闭 HTTP server、断开数据库连接、flush 日志缓冲区、保存内存状态……
  5. 如果 10 秒内进程正常退出(exit code 0),Docker 认为任务成功
  6. 如果超时未退出,Daemon 再发 SIGKILL (信号编号 9),此时内核强制终止进程,不给任何收尾机会

提示: SIGTERM 是“礼貌敲门”, SIGKILL 是“破门而入”。前者可被捕获、可延迟、可拒绝(虽然不推荐);后者不可捕获、不可忽略、立即生效。这是所有可靠服务必须区分对待的两条生命线。

我见过太多团队踩坑,根源就是没在应用代码里写 SIGTERM 处理器。比如一个 Express 应用,如果只写了 app.listen(3000) ,没加:

process.on('SIGTERM', () => {
  server.close(() => {
    console.log('HTTP server closed');
    process.exit(0);
  });
});

那么 docker stop 发来的 SIGTERM 就像石沉大海,10 秒后直接 SIGKILL ,正在处理的请求被粗暴中断,数据库连接池未释放,日志最后一行可能卡在缓冲区里——这些都不是 Docker 的错,是应用层没履行与操作系统的契约。

2.2 默认 10 秒够吗?时间不是常数,是业务特征的映射

Docker 默认 --time=10 不是拍脑袋定的。它平衡了“等待太久影响效率”和“等太短导致强制杀进程”之间的张力。但这个值对你的服务是否合理?必须结合业务特征判断:

  • Web API 服务 :通常 3~5 秒足够。HTTP server 关闭监听端口、等待活跃连接完成,一般毫秒级。
  • 数据库迁移容器 :可能需要 60~300 秒。一个 ALTER TABLE 在百万行表上可能锁表几十秒,强制 SIGKILL 会导致表结构损坏。
  • 消息队列消费者 :需等待当前消息处理完毕并 ACK,若消费逻辑复杂(如调用外部 API),10 秒远远不够。
  • 批处理作业容器 :比如 ETL 任务,可能运行数小时, docker stop 对它根本不适用——应该用 docker kill --signal=SIGUSR2 触发自定义暂停,而非终止。

我在线上环境曾将一个 Kafka 消费者服务的 --time 从 10 秒调到 120 秒,原因很简单:它的 onMessage() 方法里包含一次远程 S3 文件上传,网络抖动时上传可能卡住 90 秒。之前用默认值,每次部署都触发 SIGKILL ,导致消息重复消费(因为未 ACK)。调整后,99.8% 的停止都走 SIGTERM 优雅路径。

计算合理 timeout 的经验公式:

timeout = (最长单次业务操作耗时 × 2) + 网络波动缓冲(建议 10~30 秒) + 进程自身清理耗时(日志 flush、连接池 close 等,通常 <5 秒)

例如:一个处理 PDF 渲染的微服务,单次渲染平均 8 秒,P99 达 22 秒,网络调用最多 15 秒,则建议 --time=60

2.3 docker stop vs docker kill :不是“慢”和“快”的区别,是“可控”和“失控”的分界

这两条命令常被混用,但它们代表两种截然不同的运维哲学:

维度 docker stop docker kill
核心意图 请求优雅退出,尊重应用生命周期 强制终止,绕过所有应用层逻辑
默认信号 SIGTERM (可配置) SIGKILL (不可配置)
超时机制 有,可调( -t 无,立即生效
适用阶段 日常维护、滚动更新、CI/CD 流水线 容器僵死( ps aux 显示 <defunct> )、 docker stop 超时失败、紧急故障隔离
风险等级 低(前提是应用有 SIGTERM handler) 高(必然丢失未持久化状态、可能损坏文件系统)

注意: docker kill 支持 --signal ,但 SIGKILL 是唯一保证生效的信号。其他信号(如 SIGINT SIGUSR1 )能否生效,完全取决于容器内进程是否注册了对应 handler。没有 handler 的信号,进程会直接忽略。

真实案例:我们有个 Java Spring Boot 服务,开发时没加 @PreDestroy Runtime.addShutdownHook ,上线后每次 docker stop 都超时,最后 SIGKILL 。结果是:HikariCP 连接池未关闭,数据库侧积累大量 idle in transaction 连接,三天后 PostgreSQL 连接数打满,整个集群雪崩。解决办法不是换 docker kill ,而是补上 shutdown hook,并将 --time 设为 30 秒。

3. 四种实操方案深度解析:从基础到生产就绪

3.1 基础方案: docker stop $(docker ps -q) —— 快速但需警惕的“万能钥匙”

这是最广为人知的命令,也是新手最容易误用的。我们来逐层拆解它的真实行为:

# 第一步:docker ps -q
# 输出:所有运行中容器的短 ID(7位十六进制),每行一个
a1b2c3d
e4f5g6h
i7j8k9l

# 第二步:$() 命令替换,将输出作为参数传给 docker stop
docker stop a1b2c3d e4f5g6h i7j8k9l

# 第三步:docker stop 依次处理每个 ID(注意:是串行,非并行)
# 先 stop a1b2c3d(等它退出或超时),再 stop e4f5g6h……

优点 :极简、跨平台(Linux/macOS/WSL)、无需额外工具。

致命缺陷 :它停止 所有 运行中的容器,毫无区分度。如果你本地同时开着:

  • 一个正在跑 CI 测试的 test-db 容器(你不想停)
  • 一个共享的 redis-cache 容器(被多个项目共用)
  • 一个后台 portainer 管理界面(你正用它查日志)

这条命令会一锅端。更糟的是,它不校验容器状态——如果某个容器已处于 exited 状态但未被 rm docker ps -q 不会列出它,所以没问题;但如果容器因 OOM 被内核杀死, docker ps 可能仍显示 Up 2 hours (状态未及时刷新),这时 stop 会失败并返回非零码,但脚本若没检查 $? ,就会静默跳过,留下隐患。

实操优化技巧

  • -a 参数预览 :执行前先运行 docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}" ,人工确认列表
  • --time 统一超时 docker stop -t 30 $(docker ps -q) ,避免个别慢容器拖累全局
  • xargs 替代命令替换(更健壮) docker ps -q | xargs -r docker stop -t 30
    xargs -r 表示输入为空时不执行后续命令( docker ps -q 无输出时, xargs 直接退出,不会执行 docker stop 空参数,后者会报错)

我自己的 .zshrc 里定义的 alias 是:

alias dstop-all='docker ps -q | xargs -r docker stop -t 30 2>/dev/null || echo "No running containers to stop"'

2>/dev/null 屏蔽 docker stop 对已停止容器的警告(如 Error response from daemon: No such container: xxx ), || 后的 echo 提供友好反馈。

3.2 过滤方案:精准打击,按需关停—— docker ps --filter 的实战组合技

当你的环境容器数量超过 10 个,或存在多套隔离环境(dev/staging/prod),盲目 stop-all 就是自找麻烦。Docker 的 --filter 是真正的生产力杠杆,它让你像写 SQL WHERE 子句一样筛选目标。

核心过滤器详解(附真实场景)
过滤器语法 作用 实际案例 注意事项
--filter "status=running" 只匹配运行中容器( docker ps 默认就是,但显式写出更清晰) docker stop $(docker ps -q --filter "status=running")
--filter "ancestor=image:tag" 匹配基于指定镜像构建的容器 docker stop $(docker ps -q --filter "ancestor=my-app:dev") (停掉所有 dev 版本) ancestor 匹配镜像名+tag, my-app:latest my-app:1.2.0 是不同祖先
--filter "label=env=dev" 匹配带指定 label 的容器 docker-compose.yml labels: ["env=dev"] ,则此命令只停 dev 环境 Label 是最推荐的标记方式,语义清晰,易管理
--filter "name=^db-" 名称正则匹配( ^ 开头, $ 结尾) docker stop $(docker ps -q --filter "name=^db-") (停所有 db- 开头的容器) 需 Docker 20.10+,且正则引擎是 Go 的 regexp ,不支持 \d 等高级特性
--filter "expose=3000" 匹配暴露了指定端口的容器 docker stop $(docker ps -q --filter "expose=3000") (停所有对外提供 3000 端口的服务) expose 是 Dockerfile 的 EXPOSE 指令,非 -p 映射的端口

组合过滤实战 :假设你有以下容器:

CONTAINER ID   NAMES       IMAGE           STATUS         LABELS
a1b2c3d        web-dev     nginx:alpine    Up 2 hours     env=dev,service=web
e4f5g6h        db-dev      postgres:13     Up 3 hours     env=dev,service=db
i7j8k9l        web-stg     nginx:alpine    Up 1 hour      env=staging,service=web
j0k1l2m        redis-prod  redis:7-alpine  Up 5 hours     env=prod,service=cache
  • 只停 dev 环境的 web 和 db
    docker stop $(docker ps -q --filter "label=env=dev" --filter "label=service=web" --filter "label=service=db")
    (注意: --filter 是 AND 关系,多个 filter 同时满足)

  • 停所有非 prod 环境的容器 (排除法):
    docker stop $(docker ps -q --filter "label=env!=prod")

  • 停所有暴露 5432 端口的容器(不管环境)
    docker stop $(docker ps -q --filter "expose=5432")

提示: docker ps --filter 的输出是容器 ID 列表,但 docker stop 接受容器名、ID、ID 前缀。所以 docker stop web-dev docker stop a1b2c3d 效果相同。用名称更易读,用 ID 更精确(避免名称冲突)。

3.3 编排方案: docker-compose stop —— 微服务时代的黄金标准

当你用 docker-compose.yml 定义服务, docker-compose stop 就是停止整套环境的唯一正确姿势。它远不止是批量 stop,而是 按依赖顺序、带超时控制、尊重服务定义的智能协调器

看一个典型 docker-compose.yml

version: '3.8'
services:
  web:
    image: my-web:latest
    ports: ["3000:3000"]
    depends_on: [db, cache]
    restart: unless-stopped
  db:
    image: postgres:13
    environment:
      POSTGRES_PASSWORD: password
    volumes: ["./pgdata:/var/lib/postgresql/data"]
  cache:
    image: redis:7-alpine
    command: redis-server --appendonly yes

执行 docker-compose stop 时,Docker Compose 会:

  1. 解析 depends_on ,确定停止顺序: web cache db (依赖关系的逆序,确保上游服务先停)
  2. 对每个服务,读取其 stop_grace_period (默认 10 秒),向容器发 SIGTERM
  3. 如果服务定义了 stop_signal (如 stop_signal: SIGINT ),则发该信号而非 SIGTERM
  4. 等待所有容器退出,任一超时则报错,但不影响其他容器继续停止流程

关键优势

  • 依赖感知 :避免 db 先停, web 还在连它导致连接异常
  • 配置继承 stop_grace_period 可在 docker-compose.yml 中为每个服务单独设置,比全局 --time 精细得多
  • 状态隔离 docker-compose stop 只影响当前目录下的 docker-compose.yml 定义的服务,不会波及其他 docker run 启动的容器

生产级增强技巧

  • 为关键服务延长 grace period
    services:
      db:
        # ... 其他配置
        stop_grace_period: 120s  # 数据库停机需更长时间
      migration:
        image: my-migration:latest
        stop_grace_period: 300s  # 迁移脚本可能很慢
    
  • --timeout 覆盖 yml 配置 docker-compose stop --timeout 60 (单位秒),覆盖所有服务的 stop_grace_period
  • 选择性停止部分服务 docker-compose stop web db (只停 web 和 db,保留 cache)

我所在团队的 CI 流水线,在部署新版本前,固定执行:

# 停止旧服务(带超时)
docker-compose -f docker-compose.prod.yml stop --timeout 60
# 删除旧容器(不删 volume)
docker-compose -f docker-compose.prod.yml down --remove-orphans
# 启动新版本
docker-compose -f docker-compose.prod.yml up -d

这套组合拳确保了零停机发布(blue-green 前置步骤)。

3.4 自动化方案:Shell Alias 与脚本封装——让安全变成肌肉记忆

把常用命令固化为 alias 或脚本,不是偷懒,是把防御性编程写进 shell 环境。我的原则是: 所有涉及 docker stop 的操作,必须带明确的 scope、timeout 和 dry-run 选项

我的 alias 体系( .zshrc 片段)
# 【安全第一】带预览的 stop-all(显示将停哪些容器)
alias dstop-preview='echo "=== WILL STOP THESE CONTAINERS ==="; docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"; echo "=== CONFIRM WITH: dstop-exec ==="'

# 【执行】真正停止,带统一 30 秒超时,静默错误
alias dstop-exec='docker ps -q | xargs -r docker stop -t 30 2>/dev/null && echo "✅ All containers stopped" || echo "⚠️  Some containers failed to stop"'

# 【按标签精准停】dev 环境专用
alias dstop-dev='docker ps -q --filter "label=env=dev" | xargs -r docker stop -t 30 2>/dev/null && echo "✅ Dev containers stopped"'

# 【按名称模式停】停所有以 "test-" 开头的容器
alias dstop-test='docker ps -q --filter "name=^test-" | xargs -r docker stop -t 10 2>/dev/null && echo "✅ Test containers stopped"'

# 【Composed 停】自动识别当前目录的 docker-compose.yml 并停止
alias dstop-compose='if [ -f docker-compose.yml ]; then docker-compose stop --timeout 60; else echo "❌ No docker-compose.yml found"; fi'
进阶:Bash 脚本实现带 dry-run 的智能停止

一个更强大的方案是写成脚本,支持 --dry-run 模式:

#!/bin/bash
# save as ~/bin/dstop-safe
set -euo pipefail

DRY_RUN=false
TIMEOUT=30
FILTERS=()

while [[ $# -gt 0 ]]; do
  case $1 in
    --dry-run)
      DRY_RUN=true
      shift
      ;;
    -t|--timeout)
      TIMEOUT="$2"
      shift 2
      ;;
    --filter)
      FILTERS+=("--filter" "$2")
      shift 2
      ;;
    *)
      echo "Usage: $0 [--dry-run] [-t SECONDS] [--filter 'key=value']..." >&2
      exit 1
      ;;
  esac
done

# 构建 docker ps 命令
PS_CMD="docker ps -q"
for filter in "${FILTERS[@]}"; do
  PS_CMD="$PS_CMD $filter"
done

if [ "$DRY_RUN" = true ]; then
  echo "🔍 DRY RUN: Would execute:"
  echo "   $PS_CMD | xargs -r docker stop -t $TIMEOUT"
  echo ""
  echo "📋 Containers that would be stopped:"
  docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}" "${FILTERS[@]}"
  exit 0
fi

# 执行真实停止
echo "🛑 Stopping containers with timeout $TIMEOUTs..."
if docker ps -q "${FILTERS[@]}" | xargs -r docker stop -t "$TIMEOUT" 2>/dev/null; then
  echo "✅ Success"
else
  echo "⚠️  Some containers failed to stop (check logs above)"
  exit 1
fi

使用方式:

# 预览将停哪些容器(不执行)
dstop-safe --dry-run --filter "label=env=dev"

# 真实执行,停 dev 环境,30 秒超时
dstop-safe -t 30 --filter "label=env=dev"

# 停所有暴露 8080 端口的容器
dstop-safe --filter "expose=8080"

这个脚本的价值在于:它把“确认”变成了强制步骤。没人能绕过 --dry-run 直接执行高危操作,这比任何文档都管用。

4. 生产环境避坑指南:那些文档里不会写的血泪教训

4.1 “僵尸容器”与“孤儿卷”:强制终止后的隐形债务

docker kill docker stop 超时后 SIGKILL ,看似任务完成,实则埋下两颗雷:

雷一:僵尸进程(Zombie Process)
当容器主进程被 SIGKILL ,但其子进程(如 sh -c "python script.py" 中的 python )未被父进程回收,它们会变成僵尸进程,持续占用 PID 和少量内存。 docker ps 看不到它们,但 ps aux | grep defunct 能看到。

解决方案

  • 启动容器时加 --init (推荐): docker run --init -d my-image 。Docker 会注入一个轻量 init 进程( tini ),它能自动收割僵尸子进程。
  • 或在 Dockerfile 中用 ENTRYPOINT ["tini", "--"] (需提前安装 tini)。

雷二:孤儿卷(Orphaned Volumes)
docker stop 只停容器,不删卷。如果容器挂载了命名卷(named volume),且该卷不再被任何容器引用,它就成了“孤儿”, docker volume ls 里能看到,但 docker system prune 默认不删它(因为 prune 只删 dangling 卷,即未被任何容器引用的卷)。

后果 :磁盘空间悄悄吃满。一个日志卷每天增长 2GB,半年后就是 360GB 垃圾。

解决方案

  • 定期清理 docker volume prune -f (加 -f 跳过确认)
  • 自动化脚本 :在 CI 流水线末尾加:
    # 停止所有容器
    docker stop $(docker ps -q) 2>/dev/null || true
    # 删除所有已停止容器
    docker rm $(docker ps -a -q) 2>/dev/null || true
    # 删除所有未被引用的卷(谨慎!确保无重要数据)
    docker volume prune -f
    # 删除所有悬空镜像
    docker image prune -f
    

注意: docker volume prune 是危险操作!务必确认卷中无重要数据。生产环境应禁用此命令,改用 docker volume ls --filter "dangling=true" -q | xargs -r docker volume rm (只删 dangling 卷)。

4.2 Windows 与 macOS 的特殊陷阱:权限、路径与守护进程

Windows PowerShell 权限问题
在 PowerShell 中, docker stop $(docker ps -q) 会报错 The term 'docker' is not recognized 。这是因为 PowerShell 的命令替换语法是 $(...) ,但 docker 命令本身在 PATH 中,而子命令执行环境可能未继承 PATH。更常见的是权限不足。

正确写法

# 方案1:用 cmd 语法(兼容性最好)
docker stop $(docker ps -q)

# 方案2:PowerShell 原生语法(需管理员权限)
docker stop (docker ps -q)

# 方案3:绝对路径(最稳妥)
& "C:\Program Files\Docker\Docker\Resources\bin\docker.exe" stop (& "C:\Program Files\Docker\Docker\Resources\bin\docker.exe" ps -q)

macOS 的 Docker Desktop 问题
M1/M2 Mac 上,Docker Desktop 的 dockerd 守护进程有时会卡住,表现为 docker ps 返回空,但 ps aux | grep dockerd 显示进程在。此时 docker stop 会 hang 住。

诊断与修复

# 检查 dockerd 状态
sudo lsof -i :2376  # Docker Desktop 默认 daemon 端口
# 重启 Docker Desktop(GUI 点击右上角图标 -> Restart)
# 或命令行重启
osascript -e 'quit app "Docker"' && open -a Docker

4.3 CI/CD 流水线中的致命误区:别让 docker stop 成为单点故障

在 Jenkins/GitLab CI 中,我们常写:

before_script:
  - docker stop $(docker ps -q) 2>/dev/null || true
  - docker system prune -f

这看起来很干净,但有两大风险:

风险一:并发流水线冲突
如果两个分支的 CI job 同时运行,job A 执行 docker ps -q 得到 a1b2c3d ,job B 执行得到 e4f5g6h ,但 job A 的 docker stop a1b2c3d 可能还没结束,job B 就执行了 docker stop e4f5g6h ,如果它们共享同一个 Docker daemon(如自托管 runner),就可能互相干扰。

解决方案

  • 为每个 job 创建独立网络和卷 docker network create ci-${CI_JOB_ID} docker run --network ci-${CI_JOB_ID} ...
  • --rm 启动临时容器 docker run --rm -v $(pwd):/workspace my-builder:latest make test ,测试完自动销毁。

风险二: docker stop 失败导致流水线静默失败
docker stop 对已停止容器返回非零码,但 || true 会掩盖真实错误。如果 docker stop 因权限问题失败,后续 docker build 可能因端口占用而失败,但你根本不知道是前面的 stop 没成功。

解决方案

  • 显式检查 exit code
    if ! docker stop $(docker ps -q) 2>/dev/null; then
      echo "⚠️  Failed to stop some containers, proceeding anyway..."
    fi
    
  • docker-compose down 替代 :它更健壮,且 down 会自动删除网络、卷(可选)。

5. 常见问题速查表与独家排查技巧

问题现象 可能原因 排查命令 解决方案 我的实操心得
docker stop 后容器状态仍是 Up X hours 容器进程未响应 SIGTERM ,或 dockerd 状态未刷新 docker inspect <container> | jq '.State.Status,.State.StoppedAt'
ps aux | grep <PID>
1. 检查应用是否有 SIGTERM handler
2. 用 docker kill --signal=SIGUSR2 <container> (若应用支持)
3. 最后用 docker kill <container>
不要迷信 docker ps 的实时性 inspect 才是真相。我遇到过 ps 显示 Up ,但 inspect 显示 StoppedAt 是 5 分钟前,原因是 dockerd 进程卡死,需重启 Docker Desktop。
docker stop 返回 Error response from daemon: No such container docker ps -q 输出了已停止容器的 ID(罕见,但 dockerd bug 可能导致) docker ps -a -q | xargs -r docker inspect -f '{{.ID}} {{.State.Status}}' 2>/dev/null | grep -E "(exited|created)" docker ps -q --filter "status=running" 替代 docker ps -q 永远用 --filter "status=running" 。这是我在 37 个生产环境踩坑后总结的铁律。 docker ps -q 的“不严谨”是 Docker 的设计妥协,我们得用过滤器弥补。
停止后,宿主机端口仍被占用 容器内进程未完全退出,或 dockerd 未释放端口绑定 lsof -i :<PORT>
sudo ss -tulnp | grep <PORT>
1. docker kill <container> 强制终止
2. sudo fuser -k <PORT>/tcp (Linux)
3. 重启 dockerd
端口占用是最高频问题 。我写了个小函数 portfree() { sudo lsof -ti:$1 | xargs -r kill; } portfree 3000 一键清理。
docker-compose stop 不按 depends_on 顺序停止 depends_on 只控制启动顺序, 不控制停止顺序 (官方文档明确说明) docker-compose config --services 查看服务名
docker-compose ps 确认状态
1. 用 docker-compose down (它按依赖逆序停止)
2. 或手动 docker-compose stop service1 service2 service3 (按逆序)
depends_on 是个大坑 。很多教程说它控制启停,其实是误导。 down 才是真·依赖感知。
docker stop 超时,但 docker kill 后容器立刻消失,却报 Cannot connect to the Docker daemon dockerd 守护进程崩溃 systemctl status docker (Linux)
brew services list | grep docker (macOS)
1. sudo systemctl restart docker (Linux)
2. 重启 Docker Desktop(macOS/Windows)
守护进程崩溃往往伴随磁盘满 df -h 是第一排查命令。我见过 /var/lib/docker 分区 100%, dockerd 直接 segfault。

独家排查技巧:用 strace 追踪 docker stop 的系统调用
docker stop 行为诡异(如卡住、无响应),用 strace 看它在做什么:

# 在另一个终端,找到 docker stop 的 PID
ps aux | grep "docker stop"
# 假设 PID 是 12345
sudo strace -p 12345 -e trace=sendto,recvfrom,connect,openat

你会看到它是否在尝试连接 dockerd 的 Unix socket( /var/run/docker.sock ),或是否在 recvfrom 时阻塞——这直接指向 dockerd 是否存活。

6. 最后分享一个小技巧:用 docker events 监控停止过程

docker events 是 Docker 的实时事件总线,它能告诉你每一个容器状态变化的精确时刻。在调试停止流程时,它比 docker ps 刷新更快、更准。

开启监控:

# 新终端,实时监听 stop 事件
docker events --filter 'event=stop' --format 'Time={{.Time}} Status={{.Status}} Container={{.Actor.Attributes.name}}'

然后在另一个终端执行 docker stop my-container ,你会立刻看到:

Time=2023-10-05T08:23:41.123456789Z Status=stop Container=my-container

进阶用法 :结合 jq 实时分析:

# 统计过去 1 分钟内所有 stop 事件的耗时(需配合 timestamp)
docker events --since '1m'

更多推荐