装好 Docker、把服务跑起来之后,日常打交道最多的其实不是写配置,而是查状态、看日志、找问题。容器起不来先敲什么?磁盘满了是谁吃的?端口冲突怎么定位?这一篇就是干这个的:前半部分是按场景整理的命令速查表,后半部分是五条最常见的排障实战链路。建议收藏,出问题时按图索骥即可。
前置阅读:还没装 Docker 的先看 《如何在Linux部署Docker&Compose》;本文所有命令默认你已经能正常执行 docker 命令(在 Docker 组里或用 sudo)。
先记住一条贯穿全文的万能起手式,任何容器出问题都先走这三连:
docker ps -a # 1. 它到底是什么状态?(Exited?Restarting?Up?)docker logs <容器名> # 2. 它自己说了什么?(应用日志九成能定位)docker inspect <容器名> # 3. 它的配置和死因是什么?(退出码、挂载、端口)三连走完还没头绪,再翻到本文后面的排障实战部分对号入座。
容器类命令速查
容器是每天操作最多的对象。先看状态类:
| 命令 | 作用 | 备注 |
|---|---|---|
docker ps | 列出正在运行的容器 | 最常用,没有之一 |
docker ps -a | 列出全部容器(含已退出) | 容器「消失了」先用它找,多半是退出了 |
docker ps -a -q | 只输出容器 ID | 配合批量操作,如 docker rm $(docker ps -a -q) |
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | 自定义输出列 | 嫌默认列太宽时用 |
docker start <容器名> | 启动已存在的容器 | 和 run 不同,start 不新建容器 |
docker stop <容器名> | 优雅停止(先 SIGTERM,超时后 SIGKILL) | 默认等 10 秒,可用 -t 30 改等待时长 |
docker restart <容器名> | 重启容器 | 改了挂载文件、想让应用重读配置时用 |
docker kill <容器名> | 直接 SIGKILL 强杀 | stop 不动时再用,会丢未落盘数据 |
docker rm <容器名> | 删除已停止的容器 | 运行中的要先 stop,或 rm -f 强删(慎用) |
docker rename <旧名> <新名> | 给容器改名 | 不用重建容器就能改 |
docker run 的参数组合太多,单独拉一张表,这是实际最常用的几个:
| 参数组合 | 作用 | 备注 |
|---|---|---|
docker run -d --name app nginx | 后台运行并命名 | -d 是 detach,不加会占住终端 |
docker run -d --restart unless-stopped ... | 宿主机重启后自动拉起 | 服务器部署必加;always 与它的区别是手动 stop 后开机是否还拉起 |
docker run -p 8080:80 ... | 端口映射 宿主机:容器 | 只想本机访问写 -p 127.0.0.1:8080:80,更安全 |
docker run -v /宿主机目录:/容器目录 ... | 绑定挂载目录 | 配置、数据持久化都靠它 |
docker run -e TZ=Asia/Shanghai ... | 传环境变量 | 时区、密码类配置常用 |
docker run --rm -it ubuntu bash | 退出即删,进交互终端 | 临时测试镜像时用,不留垃圾 |
docker run -it --entrypoint sh <镜像> | 覆盖入口点进 shell 排查 | 镜像起不来、想进去看看文件时用 |
进容器和传文件:
| 命令 | 作用 | 备注 |
|---|---|---|
docker exec -it <容器名> bash | 进正在运行的容器开终端 | 没有 bash 的精简镜像换 sh |
docker exec <容器名> <命令> | 不进终端、直接在容器里执行一条命令 | 脚本里常用,如 docker exec db pg_isready |
docker cp <容器名>:/路径 /宿主机路径 | 从容器里拷文件出来 | 拷日志、拷配置出来看很方便 |
docker cp /宿主机路径 <容器名>:/路径 | 把文件拷进容器 | 临时塞个配置文件进去测试 |
docker logs -f --tail 100 <容器名> | 实时追踪最近 100 行日志 | 日志的完整用法见日志管理专篇,这里只列最常用的一条 |
docker update --restart unless-stopped <容器名> | 不重建容器、直接改它的重启策略 | 老容器当年忘了加 --restart,用它补,不用删了重建 |
docker update --memory 512m --cpus 1 <容器名> | 动态调整资源上限 | 临时救急可以,长期还是写回 compose 文件 |
TIP容器名和容器 ID 可以混用,ID 只写前几位能唯一匹配即可。但脚本里强烈建议用容器名,可读性好得多。前提是部署时都用
--name或 compose 的container_name起好名字,别用系统随机生成的那串单词。
镜像类命令速查
| 命令 | 作用 | 备注 |
|---|---|---|
docker images | 列出本机镜像 | 看 TAG 和 SIZE,排查磁盘占用先看它 |
docker pull nginx:1.27 | 拉取指定版本镜像 | 务必带版本号,别无脑 latest |
docker tag <旧标签> <新标签> | 给镜像多打一个标签 | 推送到自己仓库前必须先 tag 成仓库地址格式 |
docker rmi <镜像> | 删除镜像 | 有容器在用(哪怕已停止)会拒绝,先删容器 |
docker history <镜像> | 看镜像每一层是怎么来的、多大 | 排查「镜像为什么这么大」 |
docker inspect <镜像> | 看镜像的完整元数据 | 重点看 Entrypoint、Cmd、ExposedPorts、Env |
docker build -t myapp:1.0 . | 用当前目录 Dockerfile 构建镜像 | 打包自己的应用时用 |
docker save -o app.tar myapp:1.0 | 把镜像导出成 tar 文件 | 离线搬运、无外网环境传镜像 |
docker load -i app.tar | 从 tar 文件导入镜像 | 与 save 配对使用 |
docker image prune | 删除悬空镜像(<none> 那些) | 安全,只清构建/更新留下的无标签残层 |
离线搬运的完整流程(A 机导出、U盘/内网传过去、B 机导入):
# A 机:导出docker save -o myapp-1.0.tar myapp:1.0
# B 机:导入,镜像名和标签原样恢复docker load -i myapp-1.0.tardocker images | grep myappWARNING
docker save/load搬的是镜像,不包含容器里的数据卷。要搬整个服务(含数据),正确做法是:save 镜像 + 单独打包数据卷目录 + 带上 compose 文件,到目标机先恢复数据再docker compose up -d。只 save 镜像过去,数据库数据是不会跟着走的。
docker inspect 精讲
docker inspect 输出一大坨 JSON,是排查时信息量最大的命令。直接看全量输出太累,记住下面几个最常看的字段和 --format 取值写法就够了。
整体结构里最常翻的四块:
| 字段路径 | 看什么 | 典型用途 |
|---|---|---|
.State.Status / .State.ExitCode / .State.OOMKilled | 运行状态、退出码、是否被内存杀手干掉 | 容器退出第一时间看这里 |
.NetworkSettings.Ports | 端口映射实际生效情况 | 写了 -p 却访问不了,先确认映射在不在 |
.NetworkSettings.IPAddress | 容器在默认 bridge 上的 IP | 自定义网络下要看 .NetworkSettings.Networks 里对应网络 |
.Mounts | 数据卷/绑定挂载的源和目标 | 「我改了宿主机文件容器里没变」多半是挂载路径写错 |
.Config.Env | 容器实际拿到的环境变量 | 密码、时区没生效,先看这里是不是传进去了 |
.HostConfig.RestartPolicy | 重启策略 | 容器开机没自启,看它是不是 no |
用 --format 只取想要的值,输出干净利落:
# 取容器 IP(默认 bridge)docker inspect -f '{{.NetworkSettings.IPAddress}}' <容器名>
# 取退出码和状态,一行说清死因docker inspect -f '{{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' <容器名>
# 取自定义网络下的 IP(网络名换成自己的)docker inspect -f '{{index .NetworkSettings.Networks "mynet" "IPAddress"}}' <容器名>
# 列出全部挂载:源 -> 目标docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' <容器名>
# 看重启策略docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' <容器名>TIP查 JSON 字段还有个笨办法但很稳:
docker inspect <容器名> | grep -A2 -B2 关键词。比如想不起来 IP 字段在哪,就 grepIPAddress。先解决问题,再慢慢记字段路径。
资源与状态命令
服务跑久了要回答三个问题:谁在吃 CPU/内存?磁盘被谁占了?刚才发生了什么事?对应四组命令:
| 命令 | 作用 | 备注 |
|---|---|---|
docker stats | 实时滚动显示所有容器的 CPU、内存、网络、磁盘 IO | 像 top 一样常驻,按 Ctrl+C 退出 |
docker stats --no-stream | 只输出一次当前资源占用 | 脚本采集、快速看一眼时用 |
docker stats <容器名> | 只看指定容器 | 怀疑某个容器吃资源时锁定它 |
docker top <容器名> | 看容器里到底有哪些进程 | 等于在宿主机视角看容器的进程树 |
docker events | 实时事件流:容器启停、死亡、镜像拉取等 | 排查「容器半夜自己重启了」时回放时间线(可加 --since 1h) |
docker system df | 汇总镜像、容器、数据卷、构建缓存各占多少磁盘 | 磁盘排查的起点 |
docker system df -v | 详细列出每个镜像/卷各占多少 | 找出具体是哪个卷/镜像在膨胀 |
# 一次性看所有容器资源,按内存从大到小排个序(配合 sort)docker stats --no-stream --format "table {{.Name}}\t{{.MemPerc}}\t{{.MemUsage}}"
# 看最近 2 小时内所有容器的死亡和重启事件docker events --since 2h --filter event=die --filter event=restart
# 磁盘总账docker system df# 典型输出:# TYPE TOTAL ACTIVE SIZE RECLAIMABLE# Images 23 8 6.2GB 3.1GB (50%)# Containers 10 9 120MB 0B# Local Volumes 6 4 18GB 2GB (11%)# Build Cache 15 0 800MB 800MBdocker system df 只告诉你「哪一类」占得多,真要定位到具体目录,接着用宿主机的 du:
# Docker 的全部家当都在这里,按子目录看大小sudo du -h --max-depth=1 /var/lib/docker | sort -hr | head
# 容器日志单独有多大(日志爆磁盘的头号嫌疑人)sudo du -sh /var/lib/docker/containers/*/*-json.log | sort -hr | head网络与数据卷常用命令
这两类平时动得少,出问题时容易手生,各备一小表。
网络:
| 命令 | 作用 | 备注 |
|---|---|---|
docker network ls | 列出所有网络 | 默认有 bridge、host、none 三个 |
docker network inspect <网络名> | 看子网、网关、挂了哪些容器及其 IP | 容器互访不通时先看是不是在同一个网络里 |
docker network create mynet | 建自定义网络 | 自定义网络自带 DNS,容器可按名字互访 |
docker network connect mynet <容器名> | 把运行中的容器接入某网络 | 不用重建容器 |
docker network disconnect mynet <容器名> | 把容器从某网络断开 | |
docker network rm mynet | 删除网络 | 还有容器挂着时会拒绝 |
数据卷:
| 命令 | 作用 | 备注 |
|---|---|---|
docker volume ls | 列出所有数据卷 | 那一串随机哈希名的是匿名卷 |
docker volume inspect <卷名> | 看卷的宿主机实际路径(Mountpoint) | 备份数据就是去打包这个目录 |
docker volume create mydata | 建命名卷 | 重要数据一律用命名卷,别用匿名卷 |
docker volume rm <卷名> | 删除卷 | 数据真删,不可恢复,见下方警告 |
docker volume prune | 删除所有未被容器使用的卷 | 同样危险,老版本 Docker 无确认提示 |
WARNING数据卷相关的删除命令是整个 Docker 里最不可逆的操作:
docker volume rm和docker volume prune删的是真实数据目录,没有回收站。删之前务必先docker volume inspect确认 Mountpoint、把数据打包备份一份,再动手。生产环境的数据库卷,看到名字先停手三秒。
Compose 常用命令速查
实际部署大多用 Compose,它的命令和原生 docker 命令是两套,排障时经常混着用,这里单独备一表(在 Compose 文件所在目录执行):
| 命令 | 作用 | 备注 |
|---|---|---|
docker compose up -d | 按文件创建并启动全部服务 | 改了配置再执行一次,它只重建有变化的服务 |
docker compose ps | 看本项目的容器状态 | 比 docker ps 多一列 SERVICE,定位更快 |
docker compose logs -f <服务名> | 追踪某个服务的日志 | 不写服务名是全部服务混在一起看 |
docker compose down | 停掉并删除本项目容器和网络 | 不加 -v 不会删数据卷;加了 -v 连卷一起删,慎用 |
docker compose restart <服务名> | 只重启某个服务 | 改了挂载的配置文件时常用 |
docker compose exec <服务名> sh | 进某个服务的容器 | 和 docker exec 等效,但用服务名不用记容器名 |
docker compose pull | 批量拉取文件里所有镜像的新版 | 配合 up -d 就是手动更新全套服务 |
docker compose config | 把最终生效的配置打印出来检查 | 变量替换、多文件合并的结果都以它为准,排障神器 |
TIPCompose 排障有个高频误会:改了 Compose 文件后直接
docker compose restart没效果——restart 只是重启旧容器,不会按新配置重建。改配置(端口、环境变量、挂载)后必须docker compose up -d,它会发现差异并重建对应服务。
清理命令与红线
磁盘紧张时要清理,但 Docker 的清理命令威力分级很陡,必须先看再清、从轻到重:
| 命令 | 清什么 | 危险程度 |
|---|---|---|
docker image prune | 只清悬空镜像(无标签、无容器引用) | 低,放心用 |
docker image prune -a | 清所有未被容器使用的镜像 | 中:下次启动要重新拉取,离线环境慎用 |
docker container prune | 清所有已停止的容器 | 中:停止的容器如果还想 start 回来,先别清 |
docker builder prune | 清构建缓存 | 低,经常 build 的机器能清出不少 |
docker volume prune | 清未被使用的卷 | 高:删真实数据,见上一节警告 |
docker system prune | 组合清:停止的容器 + 悬空镜像 + 无用网络 | 中高,用前看清提示里列的清单 |
docker system prune -a --volumes | 上面全部 + 所有未用镜像 + 所有未用卷 | 极高,新手禁用 |
正确的清理姿势是三步走:
docker system df # 第一步:先看账,哪类占得多、能回收多少docker image prune # 第二步:只清最安全的悬空镜像docker system df # 第三步:复查效果,不够再考虑下一档WARNING红线:绝不要把
docker system prune -a当日常命令随手敲。 它会删掉所有当前没有容器在用的镜像——包括你特意保留的旧版本镜像、离线 save 进来的镜像、暂时停掉准备回滚的版本。网上很多「Docker 清理教程」上来就教这条,在单机玩票环境无所谓,在装了正经服务的服务器上敲一次,轻则全站重新拉镜像断网半小时,重则配合--volumes把数据库数据一起送走。清理永远从docker system df开始,按需逐项清。
排障实战一:容器启动即退出
症状:docker compose up -d 显示 Started,但 docker ps 里找不到,过一会儿 docker ps -a 里状态是 Exited (x)。
第一步看什么:日志和退出码,两个命令定生死:
docker logs <容器名> # 应用自己报的错,九成在这里docker inspect -f '{{.State.ExitCode}}' <容器名>退出码含义速查:
| 退出码 | 含义 | 常见根因 |
|---|---|---|
| 0 | 正常结束 | 不是故障:程序本身就是一次性的,或者主进程干完活自己退了 |
| 1 | 通用错误 | 应用层报错:配置写错、连不上数据库、文件不存在,看 logs |
| 2 | 参数/用法错误 | 启动命令参数写错、shell 脚本语法问题 |
| 126 | 命令不可执行 | 入口脚本没有执行权限(chmod +x 没做)、或不是可执行文件 |
| 127 | 命令不存在 | Cmd/Entrypoint 里写的程序镜像里根本没有,如精简镜像里调用 bash |
| 137 | 被 SIGKILL 强杀 | 头号嫌疑是 OOM(内存爆了被内核杀),用 inspect 看 OOMKilled 是否为 true |
| 143 | 收到 SIGTERM 后退出 | 有人/有程序执行了 stop,或宿主机在关机流程里,这不算异常 |
定位链路:
# 1. 先看日志最后 50 行docker logs --tail 50 <容器名>
# 2. 确认是不是 OOMdocker inspect -f '{{.State.OOMKilled}}' <容器名> # true 就是内存不够被杀
# 3. 怀疑镜像本身有问题:覆盖入口点进去看docker run --rm -it --entrypoint sh <镜像名>ls /app # 看文件在不在./start.sh # 手动执行启动脚本,看它当场报什么错常见根因汇总:
| 常见根因 | 说明 |
|---|---|
| 配置文件路径写错 | 挂载没生效 |
| 数据库还没起来应用先连了 | Compose 里加 healthcheck + depends_on 的 condition |
| 脚本是 Windows 换行符 | \r 会让 shebang 失效,报错很隐蔽,用 dos2unix 处理 |
| 数据目录权限不对 | 容器内非 root 用户写不进挂载目录 |
排障实战二:容器反复 Restarting
症状:docker ps 里状态一直是 Restarting (1) x seconds ago,或者 Up 几秒又变 Restarting,循环往复。
第一步看什么:先分清是「崩溃重启」还是「策略性重启」:
docker logs --tail 100 <容器名> # 看每次启动是不是同一个报错docker inspect -f '{{.RestartCount}} 重启策略={{.HostConfig.RestartPolicy.Name}}' <容器名>定位链路:
- 日志每次都是同一个错 → 本质还是「启动即退出」问题,只是 restart 策略在不停重试。按实战一的方法修根因,修好自然停。
- 日志没报错但就是重启 → 检查主进程是不是「假装在运行」:比如启动脚本最后用了后台运行
&又立刻结束,Docker 认为主进程已退出,于是按策略重启。对策是让主进程前台阻塞(如 Nginx 的daemon off;、用tail -f /dev/null临时顶住做验证)。 - Compose 里两个服务互相 depends_on → A 等 B、B 又等 A,形成循环依赖,Compose 会直接报错或行为异常。把依赖关系画出来,环上必有一条是多余的,删掉它;实在需要双向,就拆掉其中一条改用应用层重试。
常见根因:应用启动就崩(占九成)、主进程后台化、循环依赖、健康检查误杀(healthcheck 命令在精简镜像里不存在,容器被判 unhealthy 后被编排反复重建——先临时去掉 healthcheck 验证)。
临时止血:先把重启策略改掉,别让它无限刷日志:
docker update --restart no <容器名>docker stop <容器名>排障实战三:端口已被占用
症状:启动时报 Bind for 0.0.0.0:8080 failed: port is already allocated,或者 address already in use。
第一步看什么:谁占了这个端口,一条命令见分晓:
sudo ss -tlnp | grep 8080# 输出里的 users:(("nginx",pid=1234,...)) 就是占用者定位链路:
# 情况 A:是另一个容器占的docker ps --format "{{.Names}} {{.Ports}}" | grep 8080# 处理:改其中一个的宿主机端口,如 8080 -> 8081,容器内端口不用动
# 情况 B:是宿主机上的普通进程占的(如系统自带 nginx、apache)sudo systemctl stop nginx # 确认不再需要它,再考虑 disable# 或者干脆给容器换个宿主机端口,互不打扰
# 情况 C:ss 查不到占用,但 Docker 还报占用# 多半是残留的代理进程,重启 Docker 服务可解(会影响全部容器,挑低峰期)sudo systemctl restart docker常见根因:同一套 Compose 起了两遍(旧容器没删)、宿主机原生装的同类服务没关、别的项目图省事都用了 8080/3306 这类热门端口。养成习惯:每个服务规划固定的宿主机端口段,写进 compose 文件留档。
排障实战四:磁盘爆满
症状:服务器磁盘 100%,容器开始报错、数据库写不进去、docker 命令本身都可能变慢或失败。
第一步看什么:先分清是 Docker 吃的还是系统其他地方吃的:
df -h # 哪个分区满了docker system df # Docker 内部的账sudo du -h --max-depth=1 /var/lib/docker | sort -hr | head # Docker 里谁最大定位链路(Docker 场景下两大元凶):
元凶一:容器日志无限膨胀。 默认 json-file 驱动不做轮转,一个话痨容器能写出几十 GB 的日志。
# 找出最大的几个日志文件sudo find /var/lib/docker/containers -name "*-json.log" -exec du -h {} + | sort -hr | head
# 应急:清空某个容器的日志文件(不要直接删文件,用 truncate)sudo truncate -s 0 /var/lib/docker/containers/<容器ID>/<容器ID>-json.log
# 根治:给 daemon.json 配置日志轮转 max-size/max-file 后重启 Docker(只对新建容器生效,# 存量容器要重建才吃到新配置,细节见日志管理专篇)元凶二:镜像和构建缓存堆积。 反复更新、反复 build 的机器尤其明显。
docker images | wc -l # 镜像数量是否离谱docker system df -v | head -50 # 逐个看大小docker image prune # 先清悬空镜像docker builder prune # 再清构建缓存常见根因:日志没轮转(占大头)、自动更新工具没开清理旧镜像、频繁构建镜像没清缓存、数据卷里应用自己囤了大量文件(这种情况 Docker 命令清不掉,要进卷目录处理应用数据)。注意:如果 df 显示满了但 du 加起来对不上,多半是已删除但被进程占着的文件,用 sudo lsof +L1 找出来,重启对应容器即可释放。
排障实战五:容器间网络不通
症状:应用连不上同机的数据库容器,日志里全是 Connection refused、Name or service not known 或超时。
第一步看什么:确认两个容器在不在同一个网络里,这是前提:
docker network inspect <网络名> | grep -A1 '"Name"' # 看成员名单# 或者分别看两个容器挂了哪些网络docker inspect -f '{{range $k,$v:=.NetworkSettings.Networks}}{{$k}} {{end}}' <容器名>定位链路(按顺序逐项排除):
# 1. 名字解析:在应用容器里 ping 一下数据库的服务名/容器名docker exec -it <应用容器> ping -c2 db# 解析不了 -> 不在同一个自定义网络,或用了默认 bridge(默认 bridge 不支持按名解析)
# 2. 端口对不对:容器间互访用的是,不是映射到宿主机的端口# 数据库容器内监听 5432,应用就连 db:5432,哪怕宿主机映射的是 15432:5432docker exec -it <应用容器> sh -c 'wget -qO- http://api:3000/health || curl -v http://api:3000/health'# 精简镜像没工具时,用宿主机侧验证:docker exec <数据库容器> ss -tlnp # 确认服务真的在容器内监听了
# 3. 服务本身活没活docker logs --tail 50 <数据库容器> # 数据库自己没起来,网络再通也没用docker ps -a | grep db # 确认状态是 Up 不是 Exited常见根因:两个容器不在同一网络(占多数)、连错端口(拿宿主机映射端口在容器间互访)、用默认 bridge 还指望按容器名解析、目标服务根本没启动成功、目标服务只监听了 127.0.0.1(在容器内只绑回环,同网络的其他容器也连不进,要改绑 0.0.0.0)。
WARNING容器间通信永远记住三句话:同网络、喊名字、用容器端口。映射端口(
-p)是给宿主机和外网用的,跟容器互访没关系。把这三句贴在显示器边上,这类问题能少八成。
小结
- 任何故障先走万能三连:
docker ps -a看状态 →docker logs看自述 →docker inspect看死因和配置。 - 退出码是最快的分诊台:137 先查内存,127/126 查命令和权限,1 回日志里找应用报错。
- 清理的铁律是先
docker system df看账、从最安全的档位逐级清;docker system prune -a和删卷命令永远不要当顺手命令敲。 - 磁盘爆满先查日志文件和镜像两大头;端口冲突用
ss -tlnp一锤定音;网络不通按「同网络 → 服务名 → 容器端口」顺序排查。 - 命令记不住没关系,记住每类问题第一个该敲的命令,剩下的查表就行——这篇就是干这个用的。