目录

容器调试教程:使用 docker logs、exec、inspect、cp 和 stats

容器在后台运行,出错时不会直接显示在屏幕上。本节整理了最常用的调试工具,并实际重现几个常见错误。本节的示例都以一个 nginx 容器为对象:

docker run -d --name web -p 8080:80 nginx:1.30

查看日志:docker logs

容器主进程输出到标准输出(stdout)和标准错误(stderr)的内容,都可以用 docker logs 查看。先向 nginx 发送几个请求(其中 /page1 到 /page3 不存在),再查看日志:

$ docker logs --tail 3 web
2026/10/12 23:52:59 [error] 28#28: *3 open() "/usr/share/nginx/html/page3" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /page3 HTTP/1.1", host: "localhost:8080"
172.18.0.1 - - [12/Oct/2026:23:52:59 +0000] "GET /page3 HTTP/1.1" 404 153 "-" "curl/8.22.0" "-"
172.18.0.1 - - [12/Oct/2026:23:52:59 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.22.0" "-"

$ docker logs --tail 2 -t web
2026-10-12T23:52:59.403434333Z 172.18.0.1 - - [12/Oct/2026:23:52:59 +0000] "GET /page3 HTTP/1.1" 404 153 "-" "curl/8.22.0" "-"
2026-10-12T23:52:59.503902089Z 172.18.0.1 - - [12/Oct/2026:23:52:59 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.22.0" "-"
参数作用
--tail 3只看最后 3 行
-t在每行前加上 Docker 记录的时间戳
-f持续跟踪新日志(类似 tail -f),按 Ctrl + C 退出
--since 10m只看最近 10 分钟(也可以写成 2026-10-12T23:00:00)

容器停止后也能查看日志,这是排查“为什么挂了”的第一步。

stdout 与 stderr

将日志保存到文件时,你会发现一件奇怪的事:

$ docker logs --since 1m web > out.log; wc -l out.log
2026/10/12 23:52:57 [notice] 1#1: using the "epoll" event method
2026/10/12 23:52:57 [notice] 1#1: nginx/1.30.5
2026/10/12 23:52:57 [notice] 1#1: built by gcc 14.2.0 (Debian 14.2.0-19)
2026/10/12 23:52:57 [notice] 1#1: OS: Linux 5.4.0-216-generic
2026/10/12 23:52:57 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2026/10/12 23:52:57 [notice] 1#1: start worker processes
2026/10/12 23:52:57 [notice] 1#1: start worker process 28
2026/10/12 23:52:59 [error] 28#28: *1 open() "/usr/share/nginx/html/page1" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /page1 HTTP/1.1", host: "localhost:8080"
2026/10/12 23:52:59 [error] 28#28: *2 open() "/usr/share/nginx/html/page2" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /page2 HTTP/1.1", host: "localhost:8080"
2026/10/12 23:52:59 [error] 28#28: *3 open() "/usr/share/nginx/html/page3" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /page3 HTTP/1.1", host: "localhost:8080"
13 out.log

$ docker logs --since 1m web > all.log 2>&1; wc -l all.log
23 all.log

已经用 > 重定向到文件了,nginx 的启动信息和错误日志却仍然显示在屏幕上,文件里只有 13 行。原因是 docker logs 会分别保留 stdout 和 stderr:nginx 的访问记录写入 stdout,错误和启动信息写入 stderr。> 只重定向 stdout,加上 2>&1 才会把两者都保存到文件(23 行)。用 grep 过滤日志时也一样:docker logs web 2>&1 | grep error。

如果程序把日志写入容器内的文件(例如 /var/log/app.log),docker logs 就看不到。官方镜像通常会把日志重定向到 stdout / stderr;自己编写的程序也建议直接输出到 stdout。

进入容器:docker exec

在运行中的容器内运行命令:

$ docker exec web cat /etc/nginx/conf.d/default.conf | head -n 12
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

$ docker exec -u nginx web whoami
nginx

$ docker exec -e DEBUG=1 web sh -c "echo DEBUG=\$DEBUG"
DEBUG=1
写法作用
docker exec -it web bash打开一个交互式 shell(精简镜像没有 bash 时改用 sh)
docker exec web 命令运行单条命令,输出直接返回到你的终端
docker exec -u nginx web …以指定用户运行
docker exec -e KEY=VALUE web …传入环境变量

exec 会启动一个额外进程,退出(exit)不会让容器停止。

如果容器启动即退出,就无法使用 exec(容器没有在运行)。这时可以用 --entrypoint 启动一个新容器,不运行原来的服务,直接进入容器查看:

$ docker run --rm --entrypoint sh nginx:1.30 -c "ls /docker-entrypoint.d"
10-listen-on-ipv6-by-default.sh
15-local-resolvers.envsh
20-envsubst-on-templates.sh
30-tune-worker-processes.sh

在 Docker Desktop 中,点击进入容器后,也可以在 Exec 选项卡中打开 shell:

Docker Desktop 的 Exec 选项卡
Docker Desktop 容器详情页面的 Exec 选项卡,相当于 docker exec -it。图片来源:Docker 官方文档(Apache-2.0)

查看配置与状态:docker inspect

docker inspect 会输出容器的完整配置和状态(JSON),搭配 --format 提取需要的字段:

$ docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' web
running 0

$ docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
172.18.0.2

$ docker inspect --format '{{json .HostConfig.PortBindings}}' web
{"80/tcp":[{"HostIp":"","HostPort":"8080"}]}

$ docker inspect --format '{{.Path}} {{.Args}}' web
/docker-entrypoint.sh [nginx -g daemon off;]
想了解字段
停止原因.State.ExitCode、.State.Error、.State.OOMKilled
挂载了哪些文件夹.Mounts
环境变量.Config.Env
实际运行的命令.Path、.Args
IP 地址.NetworkSettings.Networks
端口映射.HostConfig.PortBindings
重启次数.RestartCount

复制文件:docker cp

在主机和容器之间复制文件,容器停止时也可以操作:

$ docker cp web:/etc/nginx/nginx.conf ./nginx.conf

$ head -n 5 nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;

$ echo "<h1>copied in</h1>" > index.html

$ docker cp ./index.html web:/usr/share/nginx/html/

$ curl -s http://localhost:8080
<h1>copied in</h1>

适合取出配置文件作参考,或临时放入一个文件进行测试。正式做法仍然是写入 Dockerfile 或使用 Volume 挂载。

资源与进程:docker stats、top、diff

$ docker stats --no-stream
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O          BLOCK I/O       PIDS
4a43d09d0bcb   web       0.00%     7.59MiB / 1.919GiB   0.39%     2.79kB / 2.5kB   15MB / 8.19kB   2

$ docker top web
PID                 USER                TIME                COMMAND
1180                root                0:00                nginx: master process nginx -g daemon off;
1238                101                 0:00                nginx: worker process
  • docker stats:CPU、内存、网络、硬盘 I/O。不加 --no-stream 会持续更新(按 Ctrl + C 退出)。未设置内存上限时,LIMIT 就是主机(或 Docker Desktop 虚拟机)的总内存。
  • docker top:查看容器内的进程。nginx 镜像中没有 ps 命令(参见第一个容器),可以从外部用 docker top 查看。
$ docker diff web
C /run
A /run/nginx.pid
C /etc
C /etc/nginx
C /etc/nginx/conf.d
C /etc/nginx/conf.d/default.conf
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp

docker diff 会列出容器可写层相对于原镜像新增(A)、修改(C)、删除(D)了哪些文件。可以看到刚才用 docker cp 放进去的 index.html 被标记为修改,default.conf 则是 nginx 启动脚本自动修改的(启用了 IPv6)。这些变更会在容器删除时消失。

常见问题示例

容器启动即退出

$ docker run -d --name broken postgres:18
Unable to find image 'postgres:18' locally
18: Pulling from library/postgres
ecc510c1e359: Already exists
4a3cafbebf6d: Pull complete
28ced28a309b: Pull complete
ab69e2551985: Pull complete
1f04dfa92b9f: Pull complete
5a487c9461a5: Pull complete
e0d4a6e58470: Pull complete
496ce2ef5b2f: Pull complete
c8145e79cc2b: Pull complete
d23c975c4693: Pull complete
06ea90d3bcc9: Pull complete
dc25540d793f: Pull complete
7967f593457f: Pull complete
Digest: sha256:74935e72241653ca55e0414067e6d8763aceb8a810eb51b452253ec3dcfc4336
Status: Downloaded newer image for postgres:18
05a9084084fc2b744e4414e95323159bbc7455a1872784176f7333d31e875bda

$ docker ps -a --filter name=broken
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS                     PORTS     NAMES
05a9084084fc   postgres:18   "docker-entrypoint.s…"   4 seconds ago   Exited (1) 4 seconds ago             broken

$ docker logs broken
Error: Database is uninitialized and superuser password is not specified.
       You must specify POSTGRES_PASSWORD to a non-empty value for the
       superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".

       You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all
       connections without a password. This is *not* recommended.

       See PostgreSQL documentation about "trust":
       https://www.postgresql.org/docs/current/auth-trust.html

docker run -d 打印出容器 ID 后,你可能以为运行成功了,但 docker ps -a 显示它几秒后就变成 Exited (1)。docker logs 直接告诉你原因:没有设置 POSTGRES_PASSWORD。排查顺序始终是:用 docker ps -a 查看状态 → 用 docker logs 查看原因。

端口已被占用

$ docker run -d -p 8080:80 nginx:1.30
c7b7a64eab9314885fb41d5ac82352c519240a17ea2e2e53098a0536a3264e6c
docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint youthful_aryabhata (aff407d4a63df5baee04263bfdc54732e45cb69016b909a8e809c9010d81e926): Bind for 0.0.0.0:8080 failed: port is already allocated

Run 'docker run --help' for more information

主机的 8080 端口已被 web 容器占用。注意,第一行仍然打印出了容器 ID:容器其实已经创建,只是启动失败,会以 Created 状态留在 docker ps -a 中,记得删除它。解决方法:换一个主机端口(-p 8081:80),或找出占用者(docker ps;如果是非 Docker 程序,则用 sudo lsof -i :8080)。

exec format error

$ docker run --rm --platform linux/arm64 alpine:3.23 uname -m
Unable to find image 'alpine:3.23' locally
3.23: Pulling from library/alpine
ace1621be7ff: Pull complete
Digest: sha256:85fe1e81d6758c208f3e1eed4338a1997e19d4be002d4dd32d3100c9a8c010a0
Status: Image is up to date for alpine:3.23
exec /bin/uname: exec format error

这台主机是 amd64(x86_64),却强制运行 arm64 版本的镜像,程序根本无法在这款 CPU 上运行。实际中最常见的情况是:在 Apple Silicon Mac 上构建的镜像,被放到普通的 x86 云主机上运行。解决方法见多平台镜像。

内存不足(OOM)

容器超出 --memory 设置的上限时,Linux 内核会强制结束进程:

  • docker ps -a 显示 Exited (137)
  • docker inspect --format '{{.State.OOMKilled}}' 容器名称 显示 true

遇到 137,但 OOMKilled 是 false,通常是被 docker kill 结束,或 docker stop 超时后强制结束。调高 --memory,或检查程序是否存在内存泄漏。

no space left on device

Docker 占用的硬盘空间太多。先用 docker system df 查看:

$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          6         2         1.026GB   757.3MB (73%)
Containers      3         2         122.9kB   4.096kB (3%)
Local Volumes   1         0         0B        0B
Build Cache     0         0         0B        0B

RECLAIMABLE 表示可以清理的部分,清理方法见清理硬盘空间。

排查流程总结

  1. docker ps -a:容器是否在运行?退出代码是多少?
  2. docker logs 容器(必要时加上 2>&1 | grep):程序本身报告了什么?
  3. 对照退出代码:1 查看日志、127 找不到命令、137 被强制结束或 OOM、139 程序崩溃(segfault)。
  4. docker inspect:确认环境变量、挂载、端口、运行的命令是否符合预期。
  5. docker run -it --rm --entrypoint sh 镜像:进入容器手动运行,看看缺少什么。

重点回顾

  • 先查看 docker ps -a 的状态与退出代码,再查看 docker logs。
  • 用 docker exec 进入运行中的容器,用 docker cp 取出文件。
  • 用 docker inspect -f 查看配置与状态,用 docker stats 查看资源用量。
  • 容器启动即退出时,用 --entrypoint sh 启动新容器手动排查。

动手练习

练习 1:运行 docker run --name broken alpine:3.23 ls /not-exist,用本章的方法找出它为什么失败,以及退出代码是多少。

答案
docker ps -a --filter name=broken
docker logs broken
docker inspect -f "{{.State.ExitCode}}" broken

日志显示 ls: /not-exist: No such file or directory,退出代码是 1。

练习 2:启动 docker run -d --name w nginx:1.30-alpine,在容器内创建一个文件后,用什么命令可以列出容器相对于镜像发生了哪些文件变更?

答案

docker diff w。A 表示新增、C 表示变更、D 表示删除,原理见底层原理。

常见问题

容器启动即退出,无法用 exec 进入怎么办?

先查看 docker logs 容器;再用 docker run --rm -it --entrypoint sh 镜像 启动一个新容器,手动运行原来的命令查看错误。

docker logs 太多看不完怎么办?

用 --tail 50 只看最后 50 行,或用 --since 10m 只看最近 10 分钟,再配合 2>&1 | grep 关键词。完整参数见 [docker logs](/zh/docker/docker-logs/)。

在哪里可以查到错误信息的含义?

参见[常见错误信息大全](/zh/docker/errors/),其中整理了 25 种错误的实际输出和解决方法。

更新于 2026年10月14日