1. 为什么第二章先把命令这块骨头啃下来先看懂Docker命令的整体地图如果你跟我一样第一章装完 Docker、跑通了hello-world大概率会有一种错觉Docker 不过如此。真正让你懵的往往是第二天你想跑一个 Nginx、想进容器里看日志、想删掉一个占了几G磁盘的镜像时突然发现自己只会docker run和docker ps其他命令基本靠猜。先别急着背命令。我的建议是先把 Docker 命令的对象模型搞清楚。Docker 的 CLI 命令看着一大堆其实翻来覆去只围绕五个对象转镜像Image、容器Container、网络Network、数据卷Volume、以及系统本身。你只需要记住一条主线docker 对象 动作。比如镜像相关的操作就是docker image这一组容器相关的是docker container这组。老版本的 Docker 允许你省略中间的对象名直接用docker pull、docker ps这种简写形式这也是大多数教程里的写法。新版本虽然官网更推荐完整写法但简写完全兼容日常使用没问题。我自己的习惯是交互式操作用简写写脚本和文档时用完整写法这样既快又能让看脚本的人知道操作对象是谁。第二章的内容我按使用频率和逻辑顺序来排不是照着帮助文档从头念到尾。这样你学完之后脑袋里会有一条清晰的从拉镜像到跑容器、从进容器到做清理的完整链路而不是一堆零散命令的堆积。2. 镜像命令组pull、images、tag、rmi、save/load 的正确打开方式2.1 拉取镜像前先学会看懂镜像名字docker pull nginx大家都会敲但镜像名里的学问值得多说两句。一个完整的镜像名长这样[仓库地址/][命名空间/]镜像名[:标签]nginx:latest省略了仓库地址默认走 Docker Hub 官方仓库mysql:8.0里的8.0是标签也就是版本号。如果你拉的是某个组织或个人的镜像名字里会多一段比如bitnami/nginx前面的bitnami是命名空间相当于发布者的账号名。如果你的公司内部有私有仓库前面还会多出一段地址类似registry.example.com/library/nginx:1.24这段地址就是你配置的镜像仓库地址。我第一次在公司环境拉镜像时就是因为没搞懂这个结构把私有仓库地址当成镜像名的一部分拼错了折腾了半天。注意latest标签是个流动的标签它指向的是最新推送的版本不是某个固定版本。生产环境里拉镜像建议指定明确的版本标签不要用latest否则哪天重新部署拉到的可能就是一个行为有变化的新版本。2.2 查看镜像images 输出怎么看docker images的输出有四个字段REPOSITORY仓库名、TAG标签、IMAGE ID、SIZE。这里有个新手容易忽略的点IMAGE ID前 12 位才是真正用来唯一标识镜像的仓库名加标签只是一串指向这个 ID 的别名。同一个镜像完全可以用多个不同的名字标记这就是docker tag存在的意义。实际使用中我最常用的是加过滤条件的写法# 只看某个仓库下的镜像 docker images nginx # 只看悬空镜像没有仓库名和标签的镜像 docker images -f danglingtrue # 不显示中间层镜像 docker images --no-truncfalse--filter这个参数非常实用特别是danglingtrue这个过滤条件。Dockerfile 里每一条指令都会生成一个中间层镜像重新构建后旧的层就会变成悬空镜像日积月累非常占磁盘这个过滤条件配合后面的清理命令是一套组合拳。2.3 给镜像打标签、删镜像、离线搬运docker tag的用法是docker tag 原镜像名 新镜像名docker tag nginx:latest myregistry.example.com/nginx:v1这个命令本身不会复制镜像只是在本地索引里新增了一个名字指向同一个 IMAGE ID。所以你在docker images里会看到两条记录但磁盘占用不会翻倍。删除镜像用docker rmi。它接的是镜像名或 IMAGE ID需要注意的是如果这个镜像已经被某个容器引用了删除会失败报错信息大概是image is being used by container。这时候你把对应容器删掉再来删镜像就正常了。离线环境搬运镜像靠的是save和load# 把镜像打包成 tar 文件 docker save -o nginx.tar nginx:1.24 # 把 tar 文件导回镜像 docker load -i nginx.tarsave和export的区别值得记住save保存的是镜像的完整层结构可以再 load 回来继续docker build时复用层缓存export导出的是容器的文件系统快照会丢掉镜像的历史和层信息。内网环境之间传镜像优先用save/load。3. 容器生命周期命令run 是整个Docker命令群的绝对核心3.1 run 的参数拆解从 --name 到 --rmdocker run是目前你接触最多的容器命令它的本质是用镜像创建并启动一个容器。参数多到能写一篇单独的文档但新人阶段真正高频的就这几个参数作用使用频率--name给容器起名字不写则随机生成每次都用-d后台运行不占用当前终端服务类必用-it交互式终端进入容器内部调试时用-p 主机端口:容器端口端口映射让宿主机能访问容器服务类必用-v 宿主机目录:容器目录挂载数据卷持久化数据有状态服务必用--rm容器停止后自动删除自身测试时推荐--restart设置容器退出后的重启策略服务类推荐一个典型的 Nginx 启动命令长这样docker run -d --name web \ -p 8080:80 \ -v /home/user/www:/usr/share/nginx/html \ --restart unless-stopped \ nginx:1.24这里-p 8080:80的意思是把宿主机的 8080 端口映射到容器内的 80 端口。容器内有自己的网络命名空间端口是隔离的不映射的话宿主机完全访问不到容器里的服务。3.2 start、stop、restart、kill、rm状态的完整闭环容器从创建到删除整个生命周期涉及这些命令# 启动一个已存在的已停止的容器 docker start web # 优雅停止容器发送 SIGTERM等待进程退出 docker stop web # 强制停止容器发送 SIGKILL立即终止 docker kill web # 重启容器 docker restart web # 删除容器-f 强制删除正在运行的容器 docker rm -f webstop和kill的区别是很多初学者的知识盲区。stop会给容器内 PID 1 进程发送 SIGTERM 信号让进程有机会做清理工作再退出相当于请关门kill直接发 SIGKILL进程没有任何机会善后相当于踹门。对于数据库这类有状态服务能stop就别kill否则可能丢数据或留下未完成的写操作。docker rm默认只能删已停止的容器加-f可以强制删除运行中的容器。这个命令执行前建议先docker stop给应用一个优雅关闭的机会特别是容器里跑的是自己写的应用时。3.3 前台还是后台-d 和 -it 到底怎么选这个选择题新手几乎都会纠结一次。规则其实很简单服务类容器Nginx、MySQL、后端 API用-d后台运行日志可以随时用docker logs查看。工具类容器想临时用一下某个环境、跑一条脚本用-it --rm容器用完即删不留下任何垃圾。调试类容器想进容器里看看目录结构、验证配置文件用-it加指定命令比如docker run -it nginx:1.24 /bin/bash。有个坑需要单独提醒很多镜像的默认 CMD 是启动主进程并占据前台如果你用-d启动了但容器几秒后自己退出了大概率是因为容器里没有前台进程在跑。Docker 的机制是容器内 PID 1 进程一退出容器就停止所以那些设计为后台守护进程的应用在容器里反而要设置成前台运行。我第一次部署 Java 应用时启动命令带了把进程放到了后台结果容器秒退排查了半天才反应过来。4. 进入正在运行的容器exec、attach、cp 的实战差异4.1 exec 和 attach一个适合调试一个适合接管容器启动之后你想进去看情况最常见的就是docker exec# 在运行中的容器里执行命令 docker exec web ls /etc/nginx # 进入容器的交互式 Shell docker exec -it web /bin/bashexec的意思是在容器里额外启动一个新进程不影响容器原本的主进程。你通过exec进到容器里随便折腾退出后容器的主进程照常运行这是最安全也最常用的调试方式。docker attach则是把当前终端绑到容器的主进程上。它做的事相当于你直接看到了主进程的 stdout并向主进程 stdin 发送输入。对 Nginx 这种进程attach上去基本没什么可交互的对 Redis、MySQL 这类有交互协议的进程attach可以让你像在前台操作数据库一样敲命令。但这有个比较大的风险你在 attach 终端里按 CtrlC会把信号发给容器主进程容器直接就停了。所以我建议日常调试优先exec不要把attach当成主要入口。还有一个细节exec 进容器后很多精简镜像比如alpine系列里没有bash只有sh。这种情况下用docker exec -it alpine-container /bin/sh才能进得去硬敲/bin/bash会报no such file or directory。4.2 从容器里拷贝文件cp 的两种方向docker cp可以在容器和宿主机之间双向拷贝文件# 从容器拷到宿主机 docker cp web:/etc/nginx/nginx.conf ./nginx.conf # 从宿主机拷进容器 docker cp ./index.html web:/usr/share/nginx/html/index.html这个命令用于快速改东西很方便。不过提醒一句用cp改容器里的文件容器一删就什么都没了。真正要持久化的文件应该通过数据卷挂载而不是靠 cp 临时改。cp适合一次性排查、取日志、改临时配置不适合作为配置管理的常态手段。5. 资源与状态查看ps、logs、top、stats 四件套5.1 ps 输出解读别只看 CONTAINER ID 那一列docker ps显示的是运行中的容器docker ps -a则把已经退出的容器也列出来。输出里的 STATUS 字段信息量很大Up 3 hours表示运行了 3 小时Exited (1) 2 minutes ago表示容器两分钟前退出了退出码是 1。退出码是排查故障的第一个线索。容器主进程退出时带的退出码和普通 Linux 进程的退出码含义一致0 是正常退出1 是通用错误137 表示被 SIGKILL 杀掉143 表示被 SIGTERM 杀掉。后面两个常见于容器内存超限被系统杀掉或者执行了docker kill。看到 137 先去看内存这几乎是个铁律。加参数控制显示范围也很常用# 列出所有容器包括已退出 docker ps -a # 只显示容器 ID方便在脚本里做批量操作 docker ps -q # 按名称过滤 docker ps --filter nameweb5.2 logs查看容器输出的正确姿势docker logs查看的是容器主进程写到 stdout 和 stderr 的日志不是看容器里的日志文件。容器里应用是否打了日志文件跟docker logs没有任何关系它只管标准输出。这点必须搞清楚否则你会在自己的应用日志文件里翻个底朝天却发现docker logs什么都看不到。常用组合# 一直跟踪输出相当于 tail -f docker logs -f web # 给每条日志加时间戳 docker logs -t web # 只看最近 100 行 docker logs --tail 100 web # 按时间范围过滤看最近 30 分钟的日志 docker logs --since 30m web如果容器异常退出先别急着docker logs从头翻到尾看看最后 50 行基本就能定位大部分问题。配合--since按时间段过滤排查几分钟前发生了什么特别高效。5.3 top 和 stats从容器视角看资源消耗docker top web展示的是容器内的进程列表其实就是在宿主机上过滤出属于该容器的进程。docker stats则是实时查看所有容器的 CPU、内存、网络和磁盘 I/O 占用这个命令对定位哪个容器在吃内存非常直观docker stats # 只看某个容器并只刷新一次后退出 docker stats --no-stream web需要注意docker stats显示的 CPU 百分比是占宿主机的比例内存则是容器内进程的总占用。如果某个容器的内存数值一直涨到接近限制回头配合退出码 137 判断基本就是内存溢出被杀的节奏。这类排查经验单靠看 Docker 文档是学不到的得自己多跑几个容器实际压一压才有体感。6. 网络与数据卷命令容器通信和数据持久化的基础操作6.1 端口映射-p 和 -P 的差别-p 8080:80是指定映射-P是自动映射把所有容器内部监听端口随机映射到宿主机的一个高位端口。-P适合快速验证一个镜像的默认行为但它映射的宿主机端口是不确定的得靠docker port web这个命令查出来实际使用频率并不高。端口映射有个细节如果宿主机端口和容器内端口都是两位数还容易看出来-p 3306:3306这种把 MySQL 的 3306 直接暴露到宿主机意味着局域网内任何人都能尝试连接你的数据库。安全起见通常建议只映射到 127.0.0.1docker run -d --name db -p 127.0.0.1:3306:3306 mysql:8.0这样只有宿主机本机能访问数据库端口外部一律被挡在门外。这个习惯在你自己开发机上有用在公司服务器上更是必备。6.2 自定义网络容器之间怎么互相访问默认情况下容器跑在bridge网络里容器之前可以互通但默认的 bridge 网络里不支持通过容器名直接解析 IP。两个容器想互相访问最简单的办法是创建一个自定义网络# 创建自定义网络 docker network create mynet # 两个容器都加入这个网络 docker run -d --name web --network mynet nginx:1.24 docker run -d --name redis --network mynet redis:7.0在自定义网络里容器之间可以直接用容器名当主机名访问比如 web 容器里访问 redis直接redis:6379就能通。自定义网络还有 DNS 自动解析能力这是默认 bridge 没有的。想确认网络配置可以docker network inspect mynet三种网络模式的区别我给个简表网络模式说明典型场景bridge默认容器有独立网络栈通过端口映射对外单机多容器互访host容器共享宿主机网络栈无端口隔离对网络性能要求极高的场景none无网络完全隔离只做计算、不联网的任务6.3 数据卷容器删除后数据还在的秘密容器是无状态的容器一删里面的数据全部消失。想持久化两大方案数据卷Volume和绑定挂载Bind Mount。数据卷由 Docker 管理放在宿主机的一个特定目录里用-v挂载时不用指定宿主机路径docker volume create mydata docker run -d --name db -v mydata:/var/lib/mysql mysql:8.0绑定挂载则是直接把宿主机的路径映射进去docker run -d --name web -v /home/user/www:/usr/share/nginx/html nginx:1.24两者选哪个我的经验应用配置和代码这类需要人工改的文件用绑定挂载数据库数据文件这类不想跟宿主机目录纠缠的用数据卷。数据卷还有一个好处用docker volume rm可以精确清理比在宿主机上找一堆散落目录要清爽。清理命令和数据卷操作还可以用docker volume prune一把删掉所有未被容器使用的卷这个放在后面维护篇一起讲。7. 构建镜像与仓库推送commit、build、push 一条龙7.1 commit临时生成镜像的快捷方式但不适合专业场景很多初学者第一次做镜像都是这么干的启动一个容器进去装了一堆软件然后docker commit把当前容器状态固化成新镜像docker commit web my-web:v1commit不是不能用它适合快速打个快照比如调试完一个环境想保留现场。但它有两个明显问题一是镜像里会把容器当时的运行时状态一起带进去比如临时文件、进程残留二是这种方式完全不可复现你根本不知道这个镜像当初装了哪些包、改了什么配置。所以可靠的做法是把构建过程写进 Dockerfile用build命令从头构建。7.2 buildDockerfile 构建的完整流程写 Dockerfile 是另外一个大话题这里只把命令行层面的用法说透。构建命令的固定格式是docker build -t my-app:v1 .最后的.是构建上下文路径也就是 Docker 会把当前目录及子目录都打包发给 Docker daemon。这个上下文路径很有讲究目录里如果有大量无关文件比如 node_modules、.git构建时会特别慢因为整个目录都要被传输。解决办法是写一个.dockerignore文件作用类似.gitignorenode_modules .git *.log构建过程中显示了Step 1/5这种进度信息说明每条指令执行的情况。如果你修改了 Dockerfile 后面的部分前面几步的执行结果会被缓存下一次构建会直接复用缓存层速度会快很多。内容有变化时缓存失效的层及其后面所有层都要重新执行。如果你发现改了一行配置构建却还是用的旧缓存可以加--no-cache强制不走缓存这个参数在排改了半天没生效的灵异问题时尤其管用。7.3 login、tag、push推送镜像到仓库的完整闭环把本地构建好的镜像推到仓库需要三步# 1. 登录仓库 docker login myregistry.example.com # 2. 给镜像打上仓库地址的标签 docker tag my-app:v1 myregistry.example.com/myteam/my-app:v1 # 3. 推送 docker push myregistry.example.com/myteam/my-app:v1push推送的是镜像及其所有分层。如果镜像体积大推送时间会很长而且中途断网可能导致推送不完整。我的建议是推送前先用docker image history看看镜像分层确认没有把本地临时文件打进镜像里再检查一下镜像体积一个动不动几个G的镜像基本可以从构建上下文里找找有没有不该存在的大文件。8. 维护与清理命令system df、prune、inspect、events 的实战价值8.1 先看磁盘空间去哪了docker system df用了一段时间 Docker你可能会发现磁盘越来越少。这时候先用docker system df看整体情况docker system df输出会分镜像、容器、本地卷、构建缓存几行分别列出空间占用。每行还有RECLAIMABLE列告诉你有多少是可以回收的一眼就能看出垃圾大概有多少。这是决定要不要执行清理的体检报告。8.2 prune 系列一键清理悬空镜像、停止容器、无用网络手动清理太痛苦prune系列是官方给的解决方案# 清理悬空镜像、停止的容器、无用网络 docker system prune # 连构建缓存一起清理占用大头通常在这里 docker system prune -a # 只清理悬空镜像 docker image prune # 清理未使用的数据卷 docker volume prune # 清理未使用的网络 docker network prunesystem prune -a会把所有没有被容器引用的镜像全部删掉包括那些带标签的镜像不只是悬空镜像。执行前它会有确认提示但注意这个提示很容易让人以为确认了就不删实际上确认后就是全删。批量清理之前先跑一下docker ps -a确认没有想保留的容器这个检查习惯能帮你避免把我最后一个旧版本镜像删了的尴尬局面。8.3 冷门但关键的两个命令inspect 和 eventsdocker inspect是排查问题时的万能放大镜# 查看容器的完整配置信息 docker inspect web # 用过滤参数只看需要的字段 docker inspect -f {{.State.Pid}} web容器启动失败、网络配置异常、挂载路径不生效这些疑难杂症最后基本都要靠inspect看原始 JSON 输出才能定位。比如你想知道容器挂在哪个网络里、挂载了哪些数据卷、Entrypoint 是什么都在 inspect 的输出里有明文答案。docker events则是个实时事件流命令容器什么时候被创建、启动、停止、删除都会以事件形式输出。调试一些比较诡异的自动化场景时很好用比如某个容器莫名其妙自己重启了docker events会告诉你它是什么时间点停的结合日志和系统时间线就能推断出原因。我也把整个第二章的命令按日常高频和排查维护两个梯度给你排个优先级第一梯队必须熟练run、ps、stop、start、restart、rm、exec、logs、images、pull、build、system df。第二梯队遇到具体问题再翻commit、push、cp、attach、top、stats、inspect、events、各类prune。先把手头第一梯队的命令练到不用看文档日常已经能跑通绝大多数业务需求了。最后分享一个我自己的习惯装好 Docker 之后找个周末写一个小项目从docker pull开始在本地完整走一遍构建镜像-起容器-挂数据卷-自定义网络互访-生产环境清理垃圾的流程。这个流程走完Docker 命令的骨架就长在你自己身上了后面再看任何高级技巧都只是在这个骨架上添砖加瓦而已。
阅读完成 · 觉得有帮助?