前四篇是按知识点讲的,这篇是反过来用的:命令忘了、线上出事了,回来翻。上半部分按场景分组整理常用命令,能直接复制;下半部分是 12 个真实场景案例,每个给出现象、怎么查、结论三步。
一、镜像
docker build -t myapp:1.0 . # 构建
docker build --no-cache -t myapp:1.0 . # 跳过缓存重新构建
docker build --build-arg VERSION=1.0 . # 传构建参数
docker images # 本地镜像列表
docker image ls --digests # 带摘要
docker history myapp:1.0 # 看分层与每层大小
docker inspect myapp:1.0 # 完整元数据
docker tag myapp:1.0 registry/team/myapp:1.0 # 打标签
docker push registry/team/myapp:1.0 # 推送
docker pull registry/team/myapp:1.0 # 拉取
docker save myapp:1.0 | gzip > myapp.tar.gz # 导出(离线交付)
docker load < myapp.tar.gz # 导入
docker rmi myapp:1.0 # 删除镜像
docker image prune -a # 删掉没被引用的镜像| 想干什么 | 命令 |
|---|---|
| 看镜像为什么这么大 | docker history --human myapp:1.0,或用 dive myapp:1.0 |
| 只保留最近几个版本 | docker image prune -a --filter "until=168h"(保留 7 天内) |
| 扫漏洞 | trivy image myapp:1.0 |
二、容器
docker run -d --name app -p 127.0.0.1:8080:8080 --restart unless-stopped myapp:1.0
docker run -it --rm --network app-net busybox sh # 一次性调试容器
docker ps # 在跑的
docker ps -a # 所有(含已退出)
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
docker start/stop/restart app
docker stop --time 30 app # 给 30 秒优雅退出
docker kill -s SIGTERM app # 只发信号不硬杀
docker rm -f app # 强制删除
docker logs -f --tail 200 app # 实时跟日志
docker logs --since 30m app # 最近 30 分钟
docker logs --until 2025-10-01T12:00:00 app # 时间区间
docker exec -it app sh # 进容器(alpine 用 sh)
docker exec app env # 看环境变量
docker cp ./conf.yml app:/etc/app/ # 拷文件进容器
docker cp app:/var/log/app.log ./ # 拷出来
docker stats --no-stream # 资源占用快照
docker top app # 容器内进程
docker inspect app --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
docker update --memory 2g --cpus 2 app # 运行中调整限制
docker wait app # 阻塞到容器退出,脚本里等结果用
docker diff app # 容器层改了哪些文件| 想干什么 | 命令 |
|---|---|
| 看容器为什么退了 | docker inspect app --format '{{.State.Error}}' + docker logs app |
| 看健康检查状态 | docker inspect app --format '{{.State.Health.Status}}' |
| 看重启了几次 | docker inspect app --format '{{.RestartCount}}' |
| 容器里没 shell | docker run --rm --network container:app --pid container:app nicolaka/netshoot ... |
三、网络
docker network ls
docker network create app-net # 自定义 bridge,支持容器名互访
docker network inspect app-net # 看谁在里面、子网分配
docker network connect app-net redis # 运行中加进来
docker network disconnect app-net redis
docker network prune # 清理没用的网络
docker exec app getent hosts mysql # 测容器内名字解析
docker exec app sh -c 'nc -zv mysql 3306' # 测端口通不通
iptables -t nat -L DOCKER -n # 宿主机上看端口映射规则关键差异:默认 bridge 不支持容器名解析,自定义网络才支持。所以多容器协作一律先 docker network create。
四、存储
docker volume create mysqldata
docker volume ls
docker volume inspect mysqldata # 看真实路径
docker volume rm mysqldata
docker volume prune # 清理没被引用的卷(先看清楚)
docker system df -v # 容器/镜像/卷各占多少
docker run -v mysqldata:/var/lib/mysql mysql:8.0 # 命名卷
docker run -v /opt/conf/my.cnf:/etc/my.cnf:ro mysql:8.0 # 只读挂载配置
docker run --mount type=bind,src=/opt/conf,dst=/etc/app,ro myapp:1.0 # 等价写法五、Compose
docker compose up -d # 后台启动
docker compose up -d --build # 重新构建镜像再启
docker compose ps # 各服务状态与健康检查
docker compose logs -f --tail 100 app
docker compose restart app
docker compose exec app sh
docker compose config # 展开最终配置,排查变量没生效
docker compose pull && docker compose up -d # 更新
docker compose down # 停并删容器(保留卷)
docker compose down -v # 连卷一起删(危险)六、系统与清理
docker system df # 总览
docker system df -v # 明细
docker system prune # 清理停止的容器、无用网络、悬空镜像
docker system prune -a --volumes # 连未使用镜像和卷一起清(危险)
docker events --since 1h # 这一小时发生了什么
docker events --since 30m --filter container=app # 只看某个容器
docker info # 存储驱动、cgroup 版本等环境信息
docker version清理前先跑一次 docker system df -v,确认要删的是什么。-a --volumes 会把没被任何容器引用的数据卷一起删掉——数据库卷如果容器停了,就属于"未引用"。
七、排障组合拳
# 1. 是不是被内核 OOM 干掉的(exit 137 第一件事)
dmesg | grep -i -E 'killed|oom' | tail -20
docker inspect app --format '{{.State.OOMKilled}}'
# 2. 资源现状
docker stats --no-stream
free -m && df -h
# 3. 容器里啥命令都没有时,借网络命名空间排查
docker run --rm --network container:app nicolaka/netshoot ss -lntp
docker run --rm --network container:app nicolaka/netshoot curl -v localhost:8080
# 4. 直接在宿主机上看容器进程(不用进容器)
docker top app
ps -ef --forest | grep -A5 $(docker inspect app --format '{{.State.Pid}}')
# 5. 看容器到底改了哪些文件
docker diff app八、常见案例说明
下面 12 个案例都是真实会遇到的,每个按「现象 → 怎么查 → 结论」三步给。
案例 1:容器一启动就退出,状态是 Exited (0)
docker ps -a # STATUS: Exited (0) 2 seconds ago
docker logs app # 往往什么都没有结论:容器里没有前台进程。PID 1 跑完就退出,容器就结束。常见于启动命令是 bash、或用了后台方式启动(service nginx start)。改成前台:nginx -g 'daemon off;',交互调试加 -it。
案例 2:exit 137,容器莫名其妙没了
docker inspect app --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
dmesg | grep -i -E 'killed|oom' | tail结论:被 SIGKILL,八成是内存超限。要么 --memory 给小了,要么应用有堆外内存泄漏,要么没配 JVM 容器感知(堆按宿主机内存算)。完整排查见Java 搬进 Docker 就水土不服与堆明明没满,容器却被 OOMKilled。
案例 3:端口映射了,外部访问不到
docker ps # PORTS 列显示 0.0.0.0:8080->8080/tcp 才算真映射
ss -lntp | grep 8080 # 宿主机上有没有在监听
curl 127.0.0.1:8080 # 先在宿主机本机试
iptables -t nat -L DOCKER -n # 看 DNAT 规则结论:三种原因——①应用只监听 127.0.0.1(容器里也是 localhost,外部进不来,要监听 0.0.0.0);②EXPOSE 写了但 docker run 没加 -p;③防火墙/安全组没放。
案例 4:容器里连不上宿主机的 MySQL
ip addr show docker0 # 通常是 172.17.0.1
docker exec app sh -c 'nc -zv 172.17.0.1 3306'结论:容器里的 localhost 是容器自己,不是宿主机。Linux 用 docker0 地址(172.17.0.1),Docker Desktop 用 host.docker.internal,或者干脆 --network host。同时要确认 MySQL 的 bind-address 不是 127.0.0.1。五个不通场景见容器里 curl localhost 永远拒绝连接。
案例 5:挂载目录 Permission denied
ls -ln /opt/data # 宿主机上看数字 uid
docker exec app id # 容器里看当前 uid结论:两边 uid 对不上。用 --user $(id -u):$(id -g) 启动,或在 Dockerfile 里建同 uid 用户。SELinux 环境(CentOS/RHEL)还要加 :z:-v /opt/data:/data:z。详见挂载目录 Permission denied 及两个难兄难弟。
案例 6:日志时间差 8 小时,定时任务对不上点
docker exec app date
docker exec app cat /etc/timezone结论:容器默认 UTC。挂 -v /etc/localtime:/etc/localtime:ro 或设 -e TZ=Asia/Shanghai。Java 应用还有三层要对齐:容器 TZ、JVM -Duser.timezone、JDBC 连接串的 serverTimezone。
案例 7:磁盘被容器日志吃满
docker system df -v # 看谁占得多
du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tail结论:json-file 驱动默认不轮转。给容器配 max-size / max-file,并在 /etc/docker/daemon.json 里设全局兜底(只对新建容器生效,老容器要重建)。复盘见根分区 100%:一个没配 max-size 的 json.log。
案例 8:构建镜像特别慢,改一行代码都要重装依赖
docker build --no-cache -t myapp:1.0 . # 先确认是不是缓存问题
docker history myapp:1.0结论:COPY 顺序错了。应该是先拷 pom.xml/package.json 装依赖,再拷源码。另外检查 .dockerignore——构建上下文太大也会拖慢。
案例 9:改了代码/配置,容器里没生效
docker diff app # 看容器层有没有变化
docker inspect app --format '{{.Mounts}}' # 确认挂载对不对结论:分三种——①文件在镜像里,改宿主机没用,必须重新 build;②bind mount 的文件改了立刻生效,但 Java 没热部署就得重启容器;③Dockerfile 里对该目录声明过 VOLUME,匿名卷会屏蔽镜像内容。
案例 10:两个容器互相 ping 不通 / 连不上
docker network inspect app-net # 确认两个容器在同一个网络
docker exec app getent hosts mysql # 名字能不能解析结论:默认 bridge(docker0)不做容器名解析。建自定义网络 docker network create app-net,两个容器都加进去,就能用容器名互访。--link 已废弃。
案例 11:容器每 40 秒重启一次,重启风暴
docker inspect app --format '{{.RestartCount}} {{.State.ExitCode}}'
docker compose logs --since 10m app | tail -50结论:启动失败 + restart: always 的死循环。典型是内存给小了,应用启动到一半被 OOM Kill。改 on-failure:5 或 unless-stopped,把资源限制调够,配健康检查让编排器知道它没起来。见容器每 40 秒重生一次。
案例 12:磁盘满了,怎么安全清理
docker system df -v # 第一步永远是看清楚
docker container prune # 只清停止的容器
docker image prune # 只清悬空镜像(<none>)
docker image prune -a --filter "until=168h" # 清 7 天前的镜像
docker volume prune # 最后再考虑卷(先看 ls!)
docker builder prune # 清构建缓存,往往能回收几 G结论:按上面顺序从安全到危险逐个来。docker volume prune 之前务必 docker volume ls 确认——停掉的容器引用的卷也算"未使用",误删就是数据丢失。真正的兜底是生产篇里的日志轮转,而不是事后清理。
备忘
- 排障顺序:看 exit code → 看 OOMKilled → dmesg → logs → inspect → stats。
- exit 137 = 被杀(多因内存);143 = 正常 SIGTERM;127 = 命令不存在;126 = 没执行权限。
- 清理磁盘按 容器 → 悬空镜像 → 老镜像 → 构建缓存 → 卷 的顺序来,
df -v先行。 - 容器里没 shell 时,用
netshoot借网络命名空间,比重新构建镜像快得多。 - 更多真实事故的排查过程在生产问题排查手记总目录。
一起聊聊