Docker常用命令与3种日志查看方式详解

发布时间:2026/10/24 3:21:35
Docker常用命令与3种日志查看方式详解

看到标题你大概也明白,我不打算再堆一遍docker help里的全部输出。日常用得上的 docker常用命令就那些,真正难的是把它们放进正确的场景;而 docker日志查看这块,几乎每个刚接触容器的人都会栽一两次跟头。这篇先把常用命令按“容器生命周期——镜像管理——调试操作”这条线理一遍,再把 3种 docker日志查看方式 拆开:docker logs、宿主机 json 日志文件、进入容器看应用日志文件。适合刚入门、想从“照抄命令”走向“理解命令”的读者,也适合已经被“容器在运行但不知道问题出在哪”折磨过的同学。

1. 生命周期命令:先把容器和虚拟机的差别揉碎

1.1 容器本质是“被隔离开的进程”

很多初学者刚接触 Docker 时,脑子里会把容器想象成一台“特别轻量的虚拟机”,于是习惯性地往里面塞 systemd、塞多个常驻服务,最后发现容器要么起不来,要么起来以后行为非常诡异。

容器的真相其实很朴素:它就是一个被命名空间、Cgroups 等机制隔离出来的进程。容器没有自己的完整内核,也谈不上“开机”和“关机”,它只是在宿主机内核上启动一个用户态进程,然后让这个进程以为自己是独立的机器。理解了这一点,很多命令的用法就豁然开朗了。

比如docker run的作用是创建并启动一个容器,但它真正做的事情其实是“启动一个进程”。这个进程如果退出了,容器就跟着变成 exited 状态。所以你会发现官方镜像里的 nginx、redis、mysql 都是以“前台进程”方式运行的,绝不会走传统虚拟机上那种“后台守护进程 + systemd 托管”的路子。

1.2 run、stop、rm 的组合链条

看一组日常最常用的生命周期命令:

# 前台运行一个一次性容器,跑完自动删除 docker run --rm hello-world # 后台运行一个 Web 服务,命名为 webapp docker run -d --name webapp -p 8080:80 nginx:alpine # 停止、启动、重启 docker stop -t 30 webapp docker start webapp docker restart webapp # 强制删除 docker rm -f webapp

-d代表 detached,让容器在后台运行,不然它会一直占着当前终端。--name是必须养成的习惯,后面绝大多数命令都可以直接用名字而不用记一长串容器 ID。--rm适用于临时容器,进程退出后顺手清理文件系统层,特别适合做实验和跑一次性脚本。

docker stop默认会给容器主进程发 SIGTERM,然后等 10 秒;如果进程还不退出,再发 SIGKILL。-t 30就是把等待时间改为 30 秒。生产环境里如果你的应用有优雅退出的逻辑,一定要把 stop 超时时间调够,否则每次停服务都像直接拔电源。docker restart是 stop + start 的组合,适合改了配置想快速重置进程的场景。

docker rm -f比较暴力,直接删容器,运行中的也会强行终止。我的建议是:除非容器状态已经没法正常 stop,否则不要把它当常规操作。删掉之前先想一想,这个容器的数据是不是写在可写层里?如果没挂载外部卷,删了就真的没了。

2. 镜像和容器的日常高频命令:认识它们才算真正上手

2.1 镜像拉取、删除和空间清理

镜像命令看起来不多,但踩坑点很集中。最典型的错误就是镜像删不掉,报错说有容器在用。这其实不是镜像命令的问题,而是没意识到镜像和容器的依赖关系。

# 搜索和拉取镜像 docker search nginx docker pull nginx:alpine # 查看本地镜像列表和详细信息 docker image ls docker image inspect nginx:alpine # 删除镜像 docker rmi nginx:alpine

docker rmi只能删掉没有被容器引用的镜像。一个镜像可能有多个版本,也可能被多个容器共用。当你看到一个镜像删不掉时,先用docker ps -a找出关联容器,把容器删掉再回来删镜像。也可以用docker image prune清理所有悬空镜像,用docker image prune -a把没被容器使用的镜像一并清掉,但这条命令要谨慎,它会连你刚拉下来还没用的镜像一起删。

docker image inspect查的是镜像元信息,比如环境变量、入口命令、暴露端口。排查“为什么这个镜像启动后行为不对劲”时,先用它看看 CMD 和 ENTRYPOINT 到底是什么,往往能省下一小时排查时间。

2.2 进入容器、拷文件、查端口

容器运行起来之后,你还需要一组“进入现场”的命令。这一组是我使用频率最高的:

# 查看所有容器,包括已经退出的 docker ps -a # 查看容器详细信息 docker inspect webapp # 获取容器 IP,-f 是格式化输出 docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' webapp # 进入容器终端 docker exec -it webapp sh # 从宿主机拷贝文件到容器,或反向拷贝 docker cp ./nginx.conf webapp:/etc/nginx/conf.d/default.conf docker cp webapp:/etc/nginx/nginx.conf ./nginx.conf.bak # 查看容器内进程和端口映射 docker top webapp docker port webapp # 查看容器可写层做了哪些改动 docker diff webapp

docker exec -it优先选 sh,因为很多精简镜像没有 bash。如果连 sh 都没有,说明镜像极度精简,需要回到 Dockerfile 层面加调试工具,或者改用别的方式排查。

docker cp是我调试配置时最常用的命令。遇到容器里配置写错的情况,我不会直接进容器里改,而是先在宿主机改好,再docker cp进去,然后docker restart。这样至少配置文件有副本,改坏了也能恢复。

docker top和docker port属于快速查看型命令。前者看容器里到底跑了哪些进程,后者看端口映射关系。尤其是docker port,配合 docker-compose 里一堆端口定义时特别好用,能一眼确认宿主机 8080 到底映射到容器的哪个端口。

docker diff是个容易被忽略的命令。它能列出容器启动后哪些文件被新增、修改、删除。排查“镜像启动后怎么多出一个奇怪文件”或者“容器里的配置是不是被运行时改过”时,它比翻日志快很多。输出里 A 代表新增,C 代表修改,D 代表删除。

3. 日志查看前必须搞懂的一件事:stdout 和文件日志不是一回事

3.1 一条日志从应用写入到被 docker logs 读到的完整路径

如果你直接去搜“docker日志查看”,大概率搜到一堆docker logs -f之类的命令。但很多人完全照着敲完之后发现,日志要么是空的,要么只有一行启动信息。问题多半不是命令写错,而是没弄明白容器的日志流动逻辑。

容器里的应用日志,有两条完全不同的去向。一条是应用把日志写到 stdout 和 stderr,这是 Docker 引擎层面的捕获通道;另一条是应用自己写文件,比如常见的logs/app.log、/var/log/xxx.log。大多数 Web 框架默认走的是第二条路,而不是 stdout。

Docker 引擎捕获 stdout/stderr 后,会交给日志驱动,默认的 json-file 驱动会把它落盘成宿主机上的 JSON 文件。你执行docker logs webapp时,引擎读取的正是这套捕获文件。整个过程可以理解为:

容器内进程写入 stdout/stderr ↓ Docker 引擎捕获 ↓ 日志驱动编码并落盘 ↓ docker logs 命令读取并展示

这条链路上任何一个环节断掉,都会导致docker logs不可用。最常见的断点就是应用根本没写 stdout,而是写进了文件。所以你在学日志查看命令之前,一定要先想清楚:我要看的日志,到底是容器的 stdout,还是应用自己的文件日志。

3.2 日志驱动:为什么有的环境里 docker logs 突然不顶用

日志驱动决定了 Docker 引擎把捕获到的输出送到哪里。默认是json-file,也就是文件形式保存在宿主机上。除此之外还有local、journald、syslog、gelf等。

在绝大多数 Linux 环境下,默认 json-file 驱动配合docker logs都很稳定。但如果你所在的环境里 Docker 守护进程被改成了 journald 驱动,日志会被交给 systemd-journald 管理,docker logs一样能读取,但原来--since、--tail这类参数在不同驱动下的表现可能会有差异。

更极端的情况是使用none驱动,它会彻底禁用日志记录,这时候docker logs返回空结果,一切排查只能靠其他手段。判断当前环境用什么驱动,可以执行:

docker info --format '{{.LoggingDriver}}'

我还遇到过一种情况:生产环境为了对接统一日志平台,配了 gelf 或 fluentd 驱动,但这套驱动本身对docker logs的支持并不完整。结果就是日志确实都存在日志平台里,可得用docker logs查的时候,感觉时有时无。这不是命令的问题,是驱动选型的问题。

所以凡是涉及日志排查,第一步永远是确认两件事:应用有没有写 stdout;当前容器用的什么日志驱动。确认完这两点,再去套用什么命令都不迟。

4. 3种docker日志查看方式:一条条从易到难讲透

4.1 方式一:docker logs 直接读标准输出

这是最顺手的查看方式,适合大多数官方镜像,因为官方镜像里的应用通常都配置成把日志打到 stdout。常用命令如下:

# 查看最近 200 行日志 docker logs --tail 200 webapp # 跟踪输出,类似 tail -f docker logs -f webapp # 只看最近 30 分钟的日志 docker logs --since 30m webapp # 带时间戳查看 docker logs --timestamps webapp

--tail一定要养成习惯。一个跑了很久的服务日志文件可能有几百兆,直接docker logs webapp会把整个文件从头到尾吐到终端,既慢又伤终端。先--tail 200看尾部,再决定要不要扩大范围。

-f是跟踪模式,适合部署完新版本后观察启动日志。此时别忘了打开另一个终端,一边执行启动命令,一边看着这里的实时输出。

--since支持30m、2h、2024-01-01T00:00:00这类写法,也可以在事后排查时快速定位某个时间点。配合--timestamps一起用,能方便地确定报错发生时刻。

实际项目里如果服务是多容器编排,比如 docker compose 起的服务,可以在服务目录下执行docker compose logs -f --tail 50,一次看所有相关容器的日志。不过它的本质也是调用引擎的日志接口,原理和docker logs一样。

4.2 方式二:在宿主机上翻 json.log 文件

当docker logs因为某些原因不太好用时,第二种方式的好处就体现出来了。json-file 驱动会把所有容器日志落盘成宿主机上的 JSON 文件,所以你完全可以绕过 Docker 引擎,直接在文件层面做查看、过滤、统计。

前提是你要能找到这个文件路径。最稳妥的方式是让 Docker 引擎告诉你:

docker inspect --format '{{.LogPath}}' webapp

输出类似于/var/lib/docker/containers/abcd1234.../abcd1234...-json.log。拿到路径后,你可以直接:

# 实时跟踪 sudo tail -f /var/lib/docker/containers/abcd1234.../abcd1234...-json.log # 用 grep 过滤关键字 sudo grep "ERROR" /var/lib/docker/containers/abcd1234.../abcd1234...-json.log | tail -50 # 按时间范围抓取 sudo awk -F '"time":"' '{print $2}' /var/lib/docker/containers/abcd1234.../abcd1234...-json.log | head

json.log 文件里每一行都是一条 JSON 记录,字段包括 log、time、stream 等。生产日志文件很大时,用grep、awk在宿主机上直接过滤,通常比docker logs更灵活,也不占用容器进程资源。

用这个方式要注意两点:第一,文件路径里的容器 ID 是完整 ID,用docker ps只能看到前 12 位,最好直接用docker inspect --format '{{.LogPath}}'取完整路径;第二,这个文件会随容器销毁而删除,一旦容器被docker rm,文件就没了,别指望事后取证。

4.3 方式三:docker exec 进容器看应用日志文件

第三种方式解决的是“应用只写文件、不写 stdout”的场景。很多 Java 后端、Python 服务、Node.js 服务,默认都把日志写到容器内的文件里。这时候你想看日志,就得进入容器去看:

# 进入容器终端 docker exec -it webapp sh # 然后进入日志目录查看 ls -lh /var/log/ tail -f /var/log/app.log

如果容器里没有 shell,也可以不进入交互终端,直接执行单条命令:

docker exec webapp tail -n 200 /var/log/app.log docker exec webapp sh -c "cat /var/log/app.log | grep ERROR | tail -50"

使用这种方式时,要注意权限问题。容器里的应用可能以普通用户身份运行,日志文件权限有限,需要加上--user root参数重新执行:

docker exec -u root webapp tail -n 200 /var/log/app.log

还要注意,很多精简镜像里没有vim、less,甚至连find都没有,所以习惯上会用tail、grep、cat这三个命令搞定大部分操作。

把三种方式放在一起看,它们的适用场景其实是互补的:

查看方式日志来源常用命令适合场景注意点
docker logsstdout/stderrdocker logs -f webapp官方镜像、标准输出型服务看不到应用写文件日志
宿主机 json.log引擎捕获的日志文件docker inspect + tail日志量大、要 grep/awk 过滤需要宿主机权限,容器删则文件删
容器内文件日志应用自行写入的文件docker exec + tail应用只写文件不写 stdout需要镜像里有 shell,注意权限

这三种方式不是单选题。线上排查时我通常的组合拳是:先用docker logs --tail 200看引擎层日志,再用docker inspect --format '{{.LogPath}}'快速判断日志落盘位置,最后进容器看应用框架自己写的日志文件。三步下来,一般就能把问题定位到具体方向。

5. 日志实战的坑:从日志文件膨胀到容器重建

5.1 应用只写文件不写 stdout,docker logs 就是空的

这是新手最容易怀疑人生的问题:容器明明在运行,应用也可能正在报错,但docker logs只有一行启动日志,甚至什么都没有。

原因就是应用没有写 stdout。比如不少 Java 应用默认的日志配置是输出到文件,控制台只打少量启动信息。你指望docker logs看到完整调试日志,完全是打错了算盘。

碰到这种情况,我见过几种处理思路。第一种是改应用日志配置,让它同时输出到文件和控制台。这是最干净的方案,也不是所有框架都支持双输出,配置时需要翻一下日志库的文档。第二种是改 Dockerfile 的入口脚本,在容器启动时用tail -f把应用日志文件实时转发到 stdout。这样做的好处是你仍然能用docker logs看日志,但要额外注意不要让 tail 变成孤儿进程,尽量用exec tail -f让它成为主进程。第三种是直接改变运维习惯,回到第 4 节的第三种方式,进容器看文件。

我的建议是,生产环境尽量走第一种和第三种,因为它们不改变容器的进程树,也不引入额外常驻进程。真要在入口脚本里转发 stdout,务必保证转发进程能拿到 SIGTERM 并正常退出,否则 stop 容器时会白白等满超时时间。

5.2 日志文件越来越大,轮转参数怎么设置才有效

json-file 驱动默认不会对日志文件做大小限制。一个高频服务跑上几天,容器日志文件可能膨胀到几个 GB,直接拖慢宿主机磁盘和 IO。

解决方式是在 Docker 守护进程配置文件里加上轮转参数。通常路径是/etc/docker/daemon.json:

{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "5" } }

这段配置的含义是:单个日志文件最大 50MB,超过后自动轮转,最多保留 5 个文件。也就是说单个容器最多占用 250MB 日志空间。改完 daemon.json 后需要重启 Docker 服务,而且这个配置只对之后新建的容器生效,已存在的容器必须重建才能应用。

如果你只想针对某个容器单独限制,不需要动全局配置:

docker run -d --name webapp \ --log-driver json-file \ --log-opt max-size=20m \ --log-opt max-file=3 \ nginx:alpine

已经膨胀到很大的历史日志,想清理又不想删除容器,可以这样处理,而不是直接rm文件:

sudo truncate -s 0 /var/lib/docker/containers/abcd1234.../abcd1234...-json.log

为什么用 truncate 而不是删除文件?因为容器进程可能还持有这个文件的文件描述符,如果你直接删除文件,磁盘空间并不会立刻释放,反而会出现“文件删了但空间没回来”的假象。用 truncate 把文件截断为 0 字节,既清了内容,又不影响容器继续写入。

5.3 容器删了日志就没了,时区还总差 8 小时

最后说两个不太起眼但很磨人的细节。

第一个是日志的存留问题。只要容器被docker rm,json.log 文件就会跟着没。就算容器是被误删,如果你没有搭建日志采集系统,那这些日志就是彻底找不回来。所以涉及多环境长期运行的容器,最好尽早把日志接入集中平台,或者至少挂到宿主机目录里。比如启动时加参数-v /var/log/webapp:/app/logs,让应用日志直接写到宿主机目录,即使容器消失,日志还在。

第二个是时区问题。docker logs --timestamps显示的时间是容器运行时所在的时区,如果容器镜像是默认的 UTC 时区,那你看到的时间和本地时间差 8 小时,排查时很容易对不上号。处理方式是启动时给容器设置时区环境变量,比如-e TZ=Asia/Shanghai,或者挂载/etc/localtime。如果已经跑起来的容器里应用日志时间不对,先别改代码,先用docker exec webapp date确认容器内系统时间。

最后分享一个我自己的习惯:写 Dockerfile 时,我会在应用日志部分尽量保留 stdout 输出,同时在启动脚本里把文件日志目录固定下来并挂载到宿主机。这样不管用本文哪种 docker日志查看方式,至少有一条路是通的。排查问题最怕的不是命令不熟,而是现场连一条日志都找不到,那就真的只能靠猜了。