1. 内容整体设计与思路拆解很多朋友第一次接触 Docker 时见到命令第一反应就是去背参数。我之前带过几个新人拿着命令表格啃了一周真到部署项目时还是手忙脚乱。原因很简单Docker 本身的命令体系并不算复杂难的是理解每条命令在“镜像、容器、数据、网络”这条主线上扮演什么角色。这篇文章我不会机械地按字母表一条条列命令而是按日常使用的真实场景拆解。把命令当成一套工具什么时候去构建镜像、什么时候只需拉起容器、什么时候必须把数据卷挂载出来、日志刷屏了又该从哪里排查。按这条思路走下来你会发现绝大多数操作就那么几十条命令而其中高频使用的可能只有十几条。先说一个核心概念镜像和容器。把镜像理解成一个“安装包”容器则是安装包运行起来后的“进程实例”。Dockerfile 负责把构建过程写清楚docker build把代码和依赖打包成镜像docker run把镜像实例化成容器docker exec进入运行中的容器做检查docker commit把某个容器当前状态固化成新镜像。这套流程就是 Docker 使用的完整闭环理解它比背诵一百个参数更有效。Docker 命令还有一个特点它分为客户端命令和守护进程daemon命令我们日常敲的所有 docker 开头的命令都是客户端命令通过 REST API 与后台守护进程通信。这就解释了为什么某些命令比如 docker build 时挂载 SSH agent在不同操作系统上表现不一致——本质上是客户端与 daemon 之间的权限和 socket 通信差异。懂得这一点之后遇到“命令明明存在但响应诡异”的情况你会先检查 daemon 日志而不是反复重敲同样的命令。再聊一个很多人忽视的角度Docker 命令的“可组合性”。命令不是孤立的docker ps查到容器 ID然后docker logs去查日志接着docker exec进去看现场最后docker inspect拉取完整元数据。把这条链路练熟排障效率会有质的提升。下面各章节我会按这条实战路径展开尽量保证每条命令都能落到“你什么时候会用它、为什么用、不用会怎样”三个问题上。2. 核心命令解析与实操要点2.1 镜像管理构建、拉取、查看与清理镜像管理是整个 Docker 操作的基石。先记住一条原则不要凭感觉拉镜像先明确版本再考虑大小和安全。最基础的工作从拉取镜像开始docker pull nginx:stable-alpine docker pull postgres:16.2这里尤其要提醒“默认 tag 陷阱”。不加 tag 时 Docker 默认拉取 latest而 latest 是一个滚动标签内容随时可能变化。生产环境如果拉了个 latest 然后直接上生产服务器过段时间再 deploy 同样的命令拉下来的镜像可能已经不是同一个内容这会带来非常隐蔽的复现问题。所以我建议项目里所有镜像引用都锁定具体 tag或者至少在 CI/CD 里固定镜像摘要digest级别。接下来查看本地镜像docker images docker image lsdocker images和docker image ls作用一致我习惯用docker images少敲几个字符。输出里的 TAG 列、IMAGE ID 列和 SIZE 列在后续清理、对比镜像时都用得上。注意 IMAGE ID 是短 ID同一镜像的不同 tag 可能共用同一个 ID因为底层镜像层是一样的。如果想查看镜像细节尤其是入口命令、暴露端口、环境变量这些元数据用 inspectdocker inspect nginx:stable-alpine docker inspect --format {{.Config.ExposedPorts}} nginx:stable-alpine这里我插一句经验用 --format 拉取单个字段比直接看全量 JSON 高效得多。排障时经常只需要确认某一个端口或某一个 env全量输出很长很乱格式化输出一下子就能定位。Go template 语法不算难最常用的就是{{.Config.Image}}、{{.State.Status}}、{{.NetworkSettings.IPAddress}}这几个路径。镜像构建的命令行是docker build -t myapp:1.0.0 . docker build --no-cache -t myapp:1.0.0 . docker build --target builder -t myapp:1.0.0 .-t是打 tag--no-cache跳过构建缓存--target在多阶段构建时指定中间阶段。多阶段构建是控制镜像体积的利器第一阶段装编译工具链第二阶段仅拷贝编译产物和运行依赖最终镜像可以小到原来的十分之一。这一步的价值在日常部署中特别明显镜像小推送就快拉取就快启动也快。镜像清理是承载“大而全”思想的反面教材。经常出现的情况是磁盘被一堆悬空镜像和无用缓存塞满然后整个宿主机的磁盘警报。清理命令如下docker image prune docker image prune -a docker system dfdocker image prune只删除没有被容器引用的悬空镜像-a则把所有未被使用的镜像全部清理。执行前最好先跑docker system df看一下空间占用分布因为 Docker 的磁盘占用不只是镜像本身还有 build cache、容器可写层、数据卷。盲目清理很容易把想保留的缓存也删掉。有一个坑我踩过好几次容器已经删除了但镜像还是删不掉。原因往往是还有中间层被另一个镜像引用或者有停止状态的容器还持有这个镜像。这时候可以先用docker ps -a把所有容器列出来把不需要的容器删干净再执行docker image prune -a大部分问题都能解决。2.2 容器生命周期从创建到销毁的完整闭环容器生命周期管理是我个人认为最核心也最容易被忽略的部分。很多人会用docker run起容器但不太清楚 run 和 create、start 之间的差别。简单讲docker run是“创建启动”的复合操作docker create只创建不启动docker start启动已存在的容器。这个差别在批量初始化或需要先配置再启动的场景下很有用。创建并启动一个常规容器docker run -d --name web-server -p 8080:80 -e ENVproduction nginx:stable-alpine docker run -it --rm ubuntu:22.04 bash重点解释几个参数-d后台运行容器不挂在前台终端。--name给容器起名字后续用名字操作比用容器 ID 方便得多。-p端口映射宿主机端口:容器端口。这里有个容易踩坑的点-p 8080:80会把宿主机的所有网络接口都绑定到 8080如果是多网卡机器要注意安全更严谨的写法是-p 127.0.0.1:8080:80只绑定回环地址。-e注入环境变量应用读取配置的常见方式。-it交互式终端。-i保持标准输入打开-t分配伪终端。只敲docker run ubuntu bash经常会看到 bash 立即退出就是因为没有分配终端。--rm容器退出后自动删除。适合临时跑命令避免生成一堆垃圾容器。查看当前运行的容器docker ps docker ps -a docker ps -a --filter statusexited --format table {{.Names}}\t{{.Status}}docker ps只看运行中的容器。排障时几乎一定会用到-a因为很多出问题的容器已经退出或处于异常状态。--filter按状态筛选配合--format自定义输出列在容器数量多的时候特别有意义。停止与删除docker stop web-server docker kill web-server docker rm web-server docker rm -f web-serverstop是优雅停止发送 SIGTERM 让进程自己清理退出kill是强行终止直接发 SIGKILL。如果 stop 之后进程没退可以考虑等几秒再 kill。rm删除容器rm -f是强制删除即使容器正在运行也会先 kill 再删。这里有个细节停止一个容器并不会自动删除它停止的容器依然占用磁盘空间容器可写层还在。所以如果你确认一个容器不会再用了记得 rm 掉别留在那儿占地方。进入运行中的容器docker exec -it web-server bash docker exec web-server ls /etc/nginx docker attach web-serverdocker exec -it web-server bash是登录容器的标准姿势。docker attach是附着到容器主进程的输入输出上容易把终端搞乱一般不推荐日常使用。还有一个常见误区是试图用docker run再起一个容器来进旧容器这是行不通的思路要记住 exec 才是在已有容器里执行命令的正确工具。容器和宿主机之间拷贝文件docker cp ./config.yml web-server:/app/config.yml docker cp web-server:/var/log/app.log ./app.logdocker cp 在调试阶段很有用比如临时往容器里塞一个配置文件或者从容器里把日志拉出来分析。但要注意这个操作不是数据持久化的替代品容器一旦删除里面的文件就全没了所以真正需要长期保存的数据还是要用数据卷。容器状态查看docker stats docker top web-server docker inspect web-serverdocker stats实时展示容器资源占用跟系统 top 类似docker top查看容器内进程docker inspect查看完整配置和状态信息。这几个配合起来能快速定位是 CPU 爆了、内存不够还是进程崩溃重启。2.3 日志查看与实时跟踪日志是排查问题最重要的信息来源之一。Docker 提供的日志命令简单直接但想用好还是有几个关键点要掌握。基础查看docker logs web-server docker logs --tail 200 web-server docker logs -f web-server docker logs --since 10m web-server--tail 200只看最后 200 行-f实时跟踪--since 10m只看最近 10 分钟的日志。这三个参数组合使用基本覆盖日常排障场景。比如一个服务突然报错通常先docker logs --tail 500 --since 30m拉最近半小时的日志再-f跟几秒看看实时输出。这里我要特别提醒一个很容易被忽略的问题容器进程如果直接把日志输出到 stdout/stderrdocker logs 才能正常获取如果进程自己写文件日志docker logs 什么都看不到。前者是 Docker 官方推荐的方式因为可以让日志统一走宿主机日志驱动方便集中采集后者则需要你通过 docker exec 进入容器去读文件或者把日志目录挂载成数据卷再在宿主机上查看。很多自研应用默认写文件日志部署到容器里发现 docker logs 没输出这是非常常见的问题不是 Docker 坏了。另外日志驱动本身也值得了解。默认的 json-file 驱动会把日志存在宿主机上长时间高并发服务的日志文件可能膨胀得很快。如果不对日志做轮转最终会把磁盘塞满。可以在运行容器时加上日志轮转参数docker run -d --name app \ --log-opt max-size10m \ --log-opt max-file3 \ nginx:stable-alpinemax-size 限制单个日志文件大小max-file 限制最多保留几个文件超出后会自动切割。这属于“用了之后才觉得值”的配置建议默认就加上。否则等到磁盘报警再处理往往已经错过了最佳排障时间。2.4 网络模式与端口映射网络这块是 Docker 命令里比较绕的一部分但实际使用频率很高。Docker 默认有三种网络模式理解它们就够了bridge、host、none。bridge 模式是默认模式容器通过虚拟网桥与宿主机通信并用 NAT 实现对外访问。日常使用最多的就是 bridge。host 模式是容器共享宿主机网络栈没有独立的 IP端口也不需要映射性能最好但隔离性差。none 模式是禁用网络一般用于一些特殊调试。查看网络列表和细节docker network ls docker network inspect bridge创建自定义网络并让容器加入docker network create my-net docker run -d --network my-net --name app1 myapp:1.0.0 docker network connect my-net app2自定义网络最核心的价值是容器间 DNS 解析。在同一个自定义网络里容器可以直接用容器名访问对方不需要记住 IP。比如 app1 要访问数据库容器 db直接在配置里写http://db:5432就行Docker 内置 DNS 会把 db 解析成对应容器的 IP。这比写死 IP 强太多了因为容器重建后 IP 会变用容器名可以避免到处改配置。端口映射这块有一个特别容易踩的坑端口冲突。如果你在宿主机上已经有一个进程占用了 8080再用docker run -p 8080:80就会报 bind: address already in use。排查方法很简单先在宿主机上确认端口占用情况。另外还有一个小细节-p 8080:80和-P大写是不同的大写-P会把 Dockerfile 里 EXPOSE 的端口随机映射到宿主机高位端口一般开发调试时图省事会用生产环境不建议因为端口不可控。端口映射还可以用--expose来标记但不映射。这个参数本身不产生实际端口映射更像是一种元数据告诉别人容器监听了哪些端口。说实话这个命令的实际作用有限我一般只在写 Dockerfile 时用 EXPOSE运行时不额外用它。2.5 数据卷与持久化容器本身是无状态的容器删除后数据就没了。所以涉及数据库、缓存、上传文件等场景必须使用数据卷volume把数据放到宿主机或外部存储上。数据卷的操作docker volume create mydata docker volume ls docker volume inspect mydata docker volume rm mydata挂载数据卷运行容器docker run -d --name db \ -v mydata:/var/lib/postgresql/data \ postgres:16.2这里-v mydata:/var/lib/postgresql/data把名为 mydata 的数据卷挂载到容器内的数据库数据目录。数据卷由 Docker 管理存放在宿主机的特定目录下使用起来最省心。还有一种方式是 bind mount直接把宿主机的目录挂到容器里docker run -d --name web \ -v /home/user/www:/usr/share/nginx/html:ro \ nginx:stable-alpinebind mount 适合开发环境想直接改代码立即生效的场景或者要把运行日志落在宿主机路径的场景。:ro表示只读挂载防止容器内写坏宿主机的目录。这里也要提一个容易混淆的点具名卷和匿名卷的区别。具名卷就是我们上面写的mydata:/path数据卷独立存在容器删了数据卷还在。匿名卷只写路径不写名字比如-v /var/lib/postgresql/dataDocker 会自动生成一个随机名字的卷。匿名卷在docker run --rm或删除容器时如果没注意容易产生大量无主卷占用空间。所以生产环境我建议一律使用具名卷。备份和迁移数据卷我常用的方法是临时起一个容器打包docker run --rm -v mydata:/data -v /backup:/backup alpine tar czf /backup/mydata.tar.gz -C /data .这条命令把 mydata 卷的内容打包到宿主机的 /backup 目录。恢复时反向操作解压即可。这个思路本质上是用一个临时容器同时挂载数据卷和备份目录执行 tar 命令。明白这个逻辑后各种备份方式都可以灵活组合。3. 实操过程与核心环节实现3.1 从 Dockerfile 到运行的完整流程演示把一个项目从代码变成运行中的容器完整流程是这样的第一步准备项目文件。假设有一个简单的静态网站目录里包含 index.html 和一个 nginx 配置模板。第二步编写 DockerfileFROM nginx:stable-alpine COPY index.html /usr/share/nginx/html/index.html EXPOSE 80这个 Dockerfile 很精简实际项目可能还要涉及安装依赖、编译、权限调整等但整体思路一致从基础镜像出发把项目产物拷进去声明监听端口。第三步构建镜像docker build -t my-static-site:1.0.0 .构建成功后会输出镜像 ID通过docker images可以确认。第四步运行容器docker run -d --name static-site -p 8080:80 my-static-site:1.0.0访问 http://localhost:8080 就能看到网站内容。若要修改发布版本重新构建新 tag 的镜像然后docker stop static-site docker rm static-site docker run ...换用新镜像即可。3.2 高效排障的组合命令链排障是我日常使用 Docker 命令最密集的场景。一套常用的组合拳如下docker ps -a | grep myapp docker logs --tail 100 -f myapp docker exec -it myapp sh docker inspect --format {{.State.Status}} | {{.State.ExitCode}} myapp先用docker ps -a确认容器当前状态。如果是 Exited看退出码很多退出码能直接对应问题类型比如 exit code 137 一般是 OOM 或被强杀exit code 1 通常是应用启动时报错。再docker logs看日志输出。如果日志信息不够docker exec进容器查看文件系统、网络、进程状态。最后docker inspect获取完整元数据比如挂载是否正常、环境变量是否齐全、网络 IP 是否正确。这套链路在实际排障里能覆盖八成以上的问题。有一个很典型的例子服务本身起不来日志里什么信息都没有docker inspect 一看发现环境变量缺少必要配置补上环境变量后容器秒起。很多问题根本不用进容器元数据信息先扫一遍往往就有答案。另外要提醒一个实操技巧容器如果一直在重启restarting 状态exec 是进不去的但 inspect 和 logs 不受影响。所以遇到 restarting 状态先看 logs再 inspect 检查 entrypoint 和 command 是否正确别急着强行 exec。3.3 镜像瘦身与多阶段构建镜像体积直接影响传输和部署效率。优化镜像体积最有效的手段是多阶段构建。举一个 Go 应用的例子FROM golang:1.22 AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 go build -o myapp . FROM alpine:3.19 RUN apk add --no-cache ca-certificates COPY --frombuilder /app/myapp /usr/local/bin/myapp ENTRYPOINT [myapp]第一阶段用完整的 golang 镜像编译产物第二阶段用精简的 alpine 镜像只拷贝编译好的二进制文件。这样最终的镜像体积可能只有几十兆而如果直接把 golang 镜像作为运行镜像体积会多出几个 G。其他常用的瘦身技巧还包括选择 alpine 版本的基础镜像通常比标准版小很多。清理安装软件时留下的缓存文件。合并 RUN 指令减少镜像层数。尽可能用官方镜像官方镜像通常已经做了基础优化。这里有一个常见谣言要澄清镜像层数少不等于体积一定小关键是最终文件系统里有多少内容。多层构建会带来构建缓存方面的好处而体积大小主要取决于最终层里的实际文件。所以别为了追求“单层”去写一堆 拼接正常按可读性分段即可同时注意及时清理无用文件。3.4 资源限制与稳定性配置生产环境跑容器资源限制是必须考虑的。不加限制的容器可能把宿主机 CPU 和内存打满影响同机其他服务。docker run -d --name app \ --memory512m \ --memory-reservation256m \ --cpus1.5 \ nginx:stable-alpine--memory是硬限制超过这个值容器会被 OOM。--memory-reservation是软限制平时按这个值调度紧急时可以超过。--cpus限制容器可用的 CPU 核数可以写小数。查看容器资源占用和状态docker stats app docker inspect --format {{.State.OOMKilled}} app如果容器频繁退出且退出码是 137检查 OOMKilled 是否为 true这是判断内存超限最直接的方法。我个人习惯在启动容器时就把内存限制设好宁可给多一点上限也不能完全不设上限否则一台机器上的多个容器会互相拖垮。3.5 容器自动重启策略容器挂了怎么办如果是开发环境手动重启就行生产环境最好让容器自己恢复。docker run -d --name app --restart unless-stopped myapp:1.0.0--restart的取值有 no、on-failure、always、unless-stopped。我的建议是除非特别明确否则用unless-stopped它在容器异常退出时自动重启但在你手动 stop 之后不会自动拉起。这样既保证服务可用性又不会违背“手动停就是要停住”的预期。always有个让人头疼的问题即使你手动 stop 了重启 Docker 服务时它还会再次自动启动容易造成意外恢复。重启策略在 docker run 之后也可以调整docker update --restart unless-stopped appdocker update 可以动态调整部分配置比如重启策略、CPU 限制、内存限制。这个命令在不停机的情况下调整参数很实用但不支持所有参数端口映射和挂载就不能改。4. 常见问题与排查技巧实录4.1 权限错误permission denied在 Linux 上使用 Docker 时经常会遇到Got permission denied while trying to connect to the Docker daemon socket原因是用户不在 docker 用户组里。两种解决方式一是把当前用户加入 docker 组需要重新登录或执行newgrp docker二是每次命令前加 sudo。我推荐用用户组方案日常操作不必反复 sudo。但要注意加入 docker 组等于把宿主机 root 级别的能力授权给你了因为 Docker 本身提供了许多特权操作通道这个权限本身需要被严肃对待。4.2 端口冲突处理docker run -d -p 8080:80 nginx:stable-alpine报错提示 bind: address already in use说明宿主机 8080 已经被占用。排查占用进程sudo ss -tlnp | grep 8080确认是被其他容器占用还是宿主机进程占用。如果是被容器占用查看容器名后决定要不要停掉如果是宿主机进程考虑换一个宿主机端口比如-p 8081:80。这里还有一个小技巧可以先在宿主机上看到 8080 被 docker-proxy 进程占用说明是一个容器映射了这个端口。4.3 镜像拉取超时与加速配置镜像拉取慢或超时基本是网络问题。常见的解决方式配置镜像加速器修改 Docker daemon 配置文件通常是 /etc/docker/daemon.json加入 registry-mirrors 配置。调整拉取超时时间。注意修改 daemon.json 后需要重启 Docker 服务然后docker info确认 Registry Mirrors 已生效。选择加速地址时建议选择可用的公共镜像源。这里的核心是“加速器只是镜像源的一层转发”如果某个源失效要及时切换到备用源。4.4 容器时间与宿主机不一致容器默认使用 UTC 时区而宿主机往往是本地时区。日志时间对不上会让排障变得非常痛苦。推荐方式是在启动容器时挂载时区文件docker run -d --name app \ -v /etc/localtime:/etc/localtime:ro \ -e TZAsia/Shanghai \ myapp:1.0.0目前大部分基础镜像都内置了 tzdata配合 TZ 环境变量就能正确显示本地时间。挂载 /etc/localtime 加:ro只读挂载防止容器内变更影响宿主机。4.5 容器内 DNS 解析异常容器里域名解析失败宿主机却正常这种情况通常出在 DNS 配置上。默认情况下 Docker 使用宿主机的 DNS 配置。如果你所在的网络环境 DNS 特殊可以在运行容器时指定 DNSdocker run -d --dns 8.8.8.8 --dns 114.114.114.114 --name app myapp:1.0.0排查时可以先docker exec进容器查看 /etc/resolv.conf确认 DNS 配置是否正常。有些场景是公司内网 DNS 与公共 DNS 混用配置不当会导致部分域名解析失败。4.6 容器剩余可写层的空间不足容器运行过程中往可写层写数据如果写的数据量很大可能把宿主机的磁盘空间耗尽。容器本身没有独立的磁盘空间概念可写层直接落在宿主机文件系统上。检查磁盘空间df -h docker system df如果宿主机磁盘确实紧张优先清理悬空镜像和停止的容器同时排查是否有容器日志过大。这也是为什么之前强调加日志轮转参数的原因日志膨胀是最常见的磁盘杀手。4.7 容器启动后立即退出容器启动后秒退是最常见的问题之一。原因可能是前台进程缺失容器主进程退出容器就会退出。很多基础镜像默认执行的命令是 shellshell 如果没有交互输入就会立刻退出所以需要指定一个前台运行的进程如 nginx -g daemon off;。应用启动报错环境变量缺失、配置错误、端口冲突等。资源限制内存不够被 OOM kill。排查顺序建议先docker logs看输出再docker inspect看 ExitCode 和 OOMKilled必要时按 3.2 节的组合命令链进一步查。只要容器至少启动过logs 里通常都会留下痕迹。4.8 docker compose 与命令的配合最后提一下 docker compose。写 docker compose 文件本质上是把 docker run 命令的参数结构化对多容器编排尤其重要。它解决的是“命令太多记不住配置重复敲太烦”的问题。一个简单的 compose 示例version: 3.8 services: web: image: nginx:stable-alpine ports: - 8080:80 volumes: - ./html:/usr/share/nginx/html:ro restart: unless-stopped对应命令操作docker compose up -d docker compose ps docker compose logs -f docker compose downdocker compose down 不会删除有名字的数据卷这是设计上的安全考虑。如果你想连数据卷一起清掉加-v参数但一定要确认数据不需要保留否则相当于删库跑路。回到最初的思路命令是工具不是目的。Docker 命令体系的本质是“用一套一致的抽象来管理镜像、容器、网络、数据”。你可以从最常用的十几条命令开始逐步掌握更高级的组合用法最终形成自己的排障路径。培养的习惯是先搞懂命令操作的对象是什么再背参数。这样不仅记得牢遇到文档里没直接给出的场景时也能自己推演出合理的命令组合。