“docker命令大全”——这几个字估计把不少人骗进来过。打开一搜,满屏全是命令列表,说实话我也干过这种事,翻了两页就关掉了。命令是罗列了,但没人告诉我哪些是日常高配、哪些是排查才用得上、哪些碰了就神仙难救。我自己前后搞了几年容器化,从单机跑项目到服务编排落地,今天是真准备把Docker命令这件事聊透,不是给你背诵手册,而是按我实际干活时的思路重新组织一遍。
先说明一下这篇的组织方式。我不会按官方文档的模块平铺,而是围绕一套实际工作流来拆:镜像从哪来、容器怎么跑起来、网络怎么打通、数据怎么保存、出了事怎么排查、最后怎么把烂摊子收干净。这套逻辑跑通之后,你会发现Docker命令根本不靠背,靠的是理解每个命令背后在操作哪一层抽象。
1. 先用5分钟理解Docker命令的底层逻辑
很多人记不住命令的核心原因,是把Docker当成一个单纯的应用来记。实际上它是一整套客户端-服务端架构:你敲的docker命令是客户端,它通过socket和后台的dockerd守护进程通信,dockerd再调用containerd、runc这些组件去真正拉起容器。
理解了这个,你就能解释很多怪现象。比如你改了/etc/docker/daemon.json里的镜像加速配置后,执行docker info发现没生效,十有八九是dockerd没重启。这个文件只会在守护进程启动时读取,跟你敲了多少次命令没关系。
再比如你查docker ps -a,列表里既有Exited状态的容器,也有Up状态的,这就是dockerd在维护一份容器元数据,存储位置通常在/var/lib/docker/containers/。有些新手直接去删这个目录下的文件,结果dockerd起来后状态错乱,这锅只能自己背。正确做法永远是让客户端去调用API,而不是物理删文件。
一句话总结命令的底层思维:所有命令都是对镜像层、容器层、网络栈、存储卷这四类对象的增删改查。只要你清楚自己对哪个对象要做什么操作,命令参数再长也能拆解清楚。
2. 镜像管理:拉取、构建、清理的高频操作
2.1 拉取镜像:千万别忽略tag
docker pull nginx和docker pull nginx:1.25-alpine看起来差不多,实际完全不同。前者拉的是latest标签,后者是精确到小版本的轻量发行版镜像。生产环境追latest就是在给自己埋雷——哪天镜像仓被覆盖了,你本地缓存没有,重新拉下来发现行为变了。
我建议拉取时养成三个习惯:明确tag、锁平台架构、指定镜像仓库地址。比如跨架构部署时,docker pull --platform linux/amd64这种参数很关键。你在一台ARM机器上拉默认x86的镜像,虽然能拉下来,跑起来也是废的。真正常用的拉取命令长这样:
# 指定平台架构拉取 docker pull --platform linux/amd64 nginx:1.25-alpine # 从私有仓库拉取(需要先登录) docker login registry.example.com docker pull registry.example.com/myservice:v1.2.32.2 查看与检查镜像:inspect是神器
docker images只是看列表,真正干活的时候要会用docker inspect和docker history。
docker inspect会返回一大坨JSON,里面藏着镜像的关键信息:架构、Entrypoint、Cmd、Env、暴露的端口、挂载的卷、层的ID列表。排查“为什么容器启动后直接退出”这种问题时,docker inspect几乎必用。
docker history则能看到镜像构建的每一层操作。有次我接手一个镜像体积异常大的项目,docker history一看,发现构建过程中竟然执行了apt-get install后又顺手删了包缓存但没删干净,多出来一层50多MB的垃圾。这类问题光看Dockerfile通常发现不了。
# 查看镜像关键信息 docker inspect nginx:1.25-alpine # 查看镜像分层历史 docker history --no-trunc nginx:1.25-alpine2.3 构建镜像的坑与优化
docker build是日常用的最多的命令之一,但十个里面八个没真正理解上下文(context)的本质。如果你在原目录里执行docker build .这种命令,本地所有文件都会被打包发送给dockerd,这是构建上下文。很多人不加.dockerignore,直接把几GB的日志、依赖包、node_modules全传过去,构建慢得跟蜗牛爬一样。
我强烈建议每个项目根目录都放一个.dockerignore,把没用的目录排除掉:
node_modules .git *.log dist .env此外要养成用--no-cache做干净构建的习惯(在怀疑缓存污染时用),以及利用构建缓存时把“改动频率低”的命令放在Dockerfile靠前位置。比如先COPY requirements.txt再执行RUN pip install,这样依赖没变时缓存始终命中;反过来先COPY全部代码再装依赖,代码一改全层缓存失效。
# 标准构建流程 docker build -t myapp:latest . # 查看构建上下文大小 docker build --no-cache -t myapp:latest . # 强制忽略缓存2.4 删除镜像:空间清理要聪明
docker rmi直接删镜像,但要注意两点:第一,一个镜像正在被运行中的容器使用时是删不掉的,必须先把容器删掉或停止;第二,docker rmi $(docker images -f dangling=true -q)可以批量清理悬空镜像,这种形式在反复构建时极其有用。
悬空镜像就是那些没有tag、只有<none>作为仓库名的镜像,每次构建新镜像覆盖旧tag时旧镜像就变悬空。攒久了会白白占用几十GB磁盘。
3. 容器生命周期管理:从创建到销毁的每个细节
容器管理这块最乱,因为参数实在是太多了。但拆开来看无非三条线:定义、监控、交互。
3.1 docker run 的核心参数拆解
docker run的参数动辄二三十个,但实际高频使用的就那么几个:-d后台运行、-it交互终端、--name给容器起名、-v挂载数据卷、-p端口映射、--restart重启策略、-e环境变量。
举个例子,一条日常启动命令的完整写法:
docker run -d \ --name myweb \ -p 8080:80 \ -v /data/nginx/html:/usr/share/nginx/html:ro \ -e TZ=Asia/Shanghai \ --restart unless-stopped \ nginx:1.25-alpine这条命令里几个细节容易踩坑。-p 8080:80里宿主机端口和容器端口一定不要搞反,搞反的后果是你在外面访问8080端口直接拒绝连接。-v后面的:ro是只读挂载,防止容器内误修改宿主机文件。--restart unless-stopped意味着dockerd重启时容器会自动拉起,但人为手动stop的容器除外。这个策略在生产里最常用。
还有一点,-d和-it是可以同时用的。觉得“我自己敲过的容器肯定要用-it”是个误区,-d -it后台运行且保留交互能力,之后随时可以用docker exec -it进入。
3.2 查看容器状态:ps和stats的区别
docker ps -a是查状态,docker stats是看资源占用。排查问题时两者结合起来:docker ps -a定位容器在不在、什么状态、退出码是多少;docker stats定位CPU、内存、网络IO是否异常。
docker ps -a的输出里有几个字段值得关注:STATUS列显示Up还是Exited,如果Exited会带上退出码。退出码非零通常意味着容器内主进程崩溃,这是排查应用问题非常重要的线索。
还有个小技巧,docker ps -a --filter "status=exited"可以只筛选已退出的容器,批量排查哪些容器是可以清理的废品,比一条条docker inspect高效得多。
3.3 进入容器:exec和attach有本质区别
docker exec -it container_id /bin/sh是在现存容器里启动新进程,这是生产里最常用的调试方式。docker attach则是把当前终端接到容器主进程上,副作用是:如果你Ctrl+C退出,可能直接把容器主进程干掉。
我的建议是永远优先用exec,不要碰attach。用exec进去之后,你心态要摆正——你是在一个缺少很多基础工具的容器里做"外科手术式"排查。很多官方镜像连vim、curl都没有,所以进入容器前最好先搞清楚环境里有什么。一条可以跳到容器网络空间调试的命令:
# 进入容器并附加到其网络命名空间执行命令 docker exec -it myweb sh -c "curl http://localhost:80/health"实际工作中我还会用docker cp把宿主机文件拷进容器,用于临时代码修复或配置注入。补一句:docker cp的路径参数方向别搞反,是从容器拷出来还是拷进去,多看一眼少走弯路。
3.4 容器生命周期操作表
| 操作 | 命令 | 特点与注意点 |
|---|---|---|
| 启动 | docker start | 只能启动已存在的容器,相当于run后的再唤醒 |
| 停止 | docker stop | 先发SIGTERM,10秒后强杀。-t参数可调等待时间 |
| 强制停止 | docker kill | 直接发SIGKILL,适合僵尸容器 |
| 重启 | docker restart | stop+start的简化 |
| 暂停 | docker pause | 冻结进程但不停止,适合临时挂起 |
| 删除 | docker rm | 只删容器不删镜像,-f强制删运行中的容器 |
4. 网络配置:端口映射与自定义网络的选择
网络是Docker最复杂、最坑的一个环节。新手最常见的问题是:默认bridge网络下容器能互相访问,但宿主机访问容器IP总不行,于是到处查资料,其实根源在于对网络模型理解不够。
4.1 三种内置网络的取舍
Docker有bridge、host、none三种网络模式。bridge是默认模式,容器有自己的虚拟IP,通过NAT访问外网;host模式直接共享宿主机网络栈,容器内的端口直接占宿主机端口;none是隔离网络,只用于特定场景。
实际选择逻辑很简单:跑单体应用、多实例应用、需要端口隔离的场景选bridge;对网络延迟极其敏感、想省去NAT转换的性能开销、或容器数量极少时选host。但host模式下容器的端口和宿主机直接共享,容易端口冲突,启动多个副本时无法同时绑同一个端口。
# 预留host模式启动命令 docker run -d --network host --name nginx-host nginx:1.25-alpine4.2 端口映射:-p的详细拆解
-p参数格式是宿主机IP:宿主机端口:容器端口/协议,三种写法都可以省略部分内容。省略宿主机IP时默认绑定0.0.0.0,也就是所有网卡接口都监听;省略宿主机IP和端口时,会随机分配一个高端口映射到容器端口。
踩过一个教训:某次我启动容器用-p 8080:80,结果外网访问正常、但内网服务通过某个特定IP访问死活不通。排查很久才发现宿主机上有个安全组策略默认拒绝非本机IP的访问,而Docker绑定的0.0.0.0把内网IP也纳入了监听。这里牵扯的链路层配置较多,不展开太多,但建议你在生产环境明确指定宿主机的内网IP来限制监听范围:
docker run -d -p 10.0.0.5:8080:80 --name myweb nginx:1.25-alpine4.3 自定义网络:容器间通信的正解
docker network create创建自定义网络,最大的价值是自带DNS解析。同网络下容器之间可以直接用容器名互相访问,而不需要关心各自的IP地址。这点对于多服务协作非常重要,尤其是在微服务架构里,容器重启后IP会变,用名字访问就稳定得多。
# 创建一个自定义网络 docker network create --driver bridge mynet # 启动两个容器并加入同一网络 docker run -d --network mynet --name db-server mariadb:10.11 docker run -d --network mynet --name web-server nginx:1.25-alpine # 在web-server容器里可以直接用名字访问db-server docker exec web-server ping db-server默认bridge网络是禁容器间用名字互访的(旧版本如此,新版有一定支持),所以一旦你决定系统性的多容器协作,就不该用默认网络,直接建一个自定义网络完事。
5. 数据持久化:volume、bind mount、tmpfs三选一
Docker容器天生是临时性的,容器一删,里面的数据全没了。所以在容器里跑数据库、存日志、传文件,你必须先想好数据怎么放。三种持久化方案各有适用范围。
5.1 bind mount:直接映射宿主机目录
-v /宿主机路径:/容器路径就是bind mount。最突出的优点是没有中间层,宿主机直接编辑文件,容器立刻能看到。缺点则是路径依赖宿主机目录结构,换个宿主机路径就乱了,且无权限校验,管理员写什么容器就能读什么。
它最适合配置类场景:把nginx配置、应用配置放宿主机的某个目录,容器内直接引用。比如我常用这种写法让容器读宿主机上的证书文件:
docker run -d \ --name web \ -v /etc/ssl:/etc/nginx/ssl:ro \ -p 443:443 \ nginx:1.25-alpine5.2 data volume:Docker负责管数据
docker volume create创建的卷由Docker统一管理,数据实际存放在/var/lib/docker/volumes/下。这种方案的好处是:备份迁移可以用docker volume命令操作,容器删除后卷还在,数据不丢。
我建议跑数据库类容器时,用命名volume来做数据目录。比如MySQL的镜像卷挂载,容器重启、重新创建都不影响数据:
# 创建数据卷 docker volume create mysqldata # 挂载到容器 docker run -d \ --name mydb \ -v mysqldata:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ mysql:8.05.3 tmpfs:内存中的临时文件
--tmpfs参数把临时目录挂到内存里,数据不会写到磁盘,容器重启即清空。适合存一些临时会话、缓存数据。注意tmpfs不适合进程间共享,也不适合大规模数据,数据量一大内存就被吃光了。
三种方案对比用一句话总结:配置类用bind mount,数据类用volume,缓存类用tmpfs。
6. 排错实战:从日志、进程、资源三层定位容器问题
容器出问题怎么查?这是大家都关心的部分。我自己的排错流程基本固定分三步:
6.1 第一步:docker logs 看应用日志
docker logs container_id是最直接的入口。--tail看最后多少行,-f实时跟踪,这两个参数组合起来就是标准操作:
# 查看最近100行日志并持续跟踪 docker logs --tail 100 -f myweb # 带时间戳输出,方便对齐事件 docker logs -t myweb如果日志量巨大,docker logs拉取会很卡。这时可以临时调整日志驱动配置,让日志按大小滚动,避免撑爆磁盘:
# 修改 daemon.json 后重启 dockerd { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }注意:改完daemon.json后要systemctl restart docker才能让日志轮转配置生效,但重启dockerd时所有非restart策略的容器会停止,所以线上操作要谨慎。
6.2 第二步:docker top 与 docker stats 看系统状态
docker top输出容器内进程列表,能看到进程PID、用户、启动命令;docker stats则是动态显示所有运行中容器的CPU、内存、网络、块IO。
排查“容器假死”问题时,stats和top放在一起用效果很好。比如我遇到过容器显示Up状态,但业务请求就是无响应,docker stats发现CPU占用100%,docker top查出容器内有个进程在死循环,直接定位到是应用bug而不是容器策略问题。
在容器里查进程排序:
# 查看容器内CPU占用最高的五个进程 docker top myweb -o pid,pcpu,comm | sort -k2 -rn | head -56.3 第三步:docker inspect 看配置细节
当应用和资源都正常,问题还查不出来的时候,就该看配置了。docker inspect能告诉你容器用的什么镜像、环境变量、网络模式、挂载卷、重启策略、健康检查状态。有次排查端口不通,一查发现容器里设置的环境变量PORT是5000,但手动映射的是8080,端口错位问题一眼暴露。
# 只查看关键字段 docker inspect --format='{{.State.Status}} | {{.NetworkSettings.IPAddress}} | {{.Config.Env}}' myweb--format语法需要一点Go模板知识,用熟了效率非常高。上面这条命令一次输出容器状态、IP、环境变量,排错时我能一眼看出问题在哪。
6.4 事件监听与资源限制配额
docker events监听Docker守护进程的事件流。容器创建、销毁、OOM、健康检查失败都会产生事件。线上配合脚本做告警很有用,比如:
# 监控容器异常退出事件 docker events --filter 'event=die' --filter 'type=container' --format '{{.Actor.Attributes.name}} died at {{.Time}}'资源限制这块,--memory、--cpus这两个参数一定要加。不加限制的话,某个容器内存泄漏能把整台机器拖垮,还是那句话:容器不是物理机,它是共享宿主机资源的承租人。
# 限制容器最多使用1GB内存和50%的CPU docker run -d --name limited --memory=1g --cpus=0.5 nginx:1.25-alpineOOM事件发生时的排查方法:docker inspect里能看到OOMKilled: true,配合docker events能看到OutOfMemory事件。这类问题优先看应用有没有内存泄漏,而不是无脑加内存限制。
7. 系统空间回收:journalctl、prune与日志处理的实战教训
这节必须写。跑Docker最容易被忽视的、也是迟早炸雷的,就是磁盘空间。我踩过的最大坑:某天系统无预警不可用,查了一圈发现/var/lib/docker下日志文件把磁盘占满了。
7.1 docker system df 做体检
docker system df是了解当前资源占用的一站式入口。它能展示镜像、容器、数据卷、构建缓存四个维度的数量和占用空间。建议定期执行,养成习惯。
docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 47 12 15.23GB 9.654GB Containers 38 8 205.4MB 197.2MB Local Volumes 6 3 98.1MB 40.74MB Build Cache -- -- 12.6GB 12.6GB看到RECLAIMABLE巨大的时候,就是该动手清理的信号了。
7.2 prune 系列命令的安全用法
docker system prune -a会删除所有无tag镜像、所有停掉的容器、所有未被使用的网络和构建缓存,执行前最好深呼吸三秒,确认没有误删的风险。未运行的容器里可能还有你需要的日志或临时数据,prune一开跑就全没了。
更保险的做法是只清理指定维度:
# 只清理悬空镜像 docker image prune # 只清理已退出的容器 docker container prune # 清理一个数据卷(慎用!) docker volume rm mysqldata关键原则:删容器时顺手确认是否有未备份的卷数据,特别是数据库类容器。docker volume ls可以作为删除前置检查命令。
7.3 日志文件导致的"假性空间不足"
某次容器正常运行,但磁盘却满了。排查后发现在/var/lib/docker/containers/下积了几个巨大的*-json.log文件,这些是默认日志驱动产生的容器日志,不轮转、不限制大小。给这个场景打个补丁,最有效的手段是全局启用日志限制,上一节已经给了daemon.json的样例。
如果已经中招、磁盘只剩几百MB,应急处理可以直接把某容器日志清空(服务不用重启):
# 清空某个容器的日志文件(会丢失日志内容) truncate -s 0 /var/lib/docker/containers/$(docker inspect --format='{{.Id}}' myweb)/*-json.log这个手段应急很好用,但要清楚它是“物理截断日志文件”,不是Docker管理的操作。清空后应用进程继续写日志没有问题,只是历史日志全没了。所以只作为磁盘告急时的急救方案,不能作为常规手段。
8. 从入门到"够用":我的随身命令清单
写到最后,给你提个醒:Docker命令不需要全记,你又不是搜索引擎。根据我自己的使用频率,真正值得刻在脑子里的命令其实不超过二十条。
下面这份清单是我交给刚入行同事的最小集,每条都是日常跑项目真正会用到的:
# 镜像操作四件套 docker pull <镜像:tag> docker images docker build -t <镜像名>:<tag> . docker rmi <镜像ID> # 容器核心操作 docker run -d --name <名字> -p 宿主机端口:容器端口 -v 宿主机目录:容器目录 <镜像> docker ps -a docker logs -f <容器名> docker exec -it <容器名> sh docker stop <容器名> docker start <容器名> docker restart <容器名> docker rm <容器名> # 排查三板斧 docker inspect <容器名> docker top <容器名> docker stats # 空间管理三件套 docker system df docker system prune docker volume ls把这份清单吃透了,配合前面讲的排查思路,你完全可以在实际项目里玩转Docker,而不是翻着命令大全敲一行试一行。
还有几个经验供参考。第一,顺手给容器起好名字,别用随机生成的那串乱码,起名字的成本极低但排查效率翻倍。第二,一个小技巧:每次构建镜像时把日期和git提交号打在tag里,比如myapp-20240615-a1b2c3,再配上docker tag命令,你随时能知道线上跑的是哪个版本。第三,做清理操作前先执行docker system df看清局势,再决定是rm还是prune,宁可多按几次docker ps -a也不要盲删。
这套流程经历过开发和运维两侧磨合,已经相对顺手。但工具是死的,思路是活的,动手前多想几步总比事后救火强。多动几次手,这些命令迟早成为你的肌肉记忆。