作为一个几乎每天都在跟 Docker 打交道的人我越来越觉得真正的工作流核心其实就那么二三十个命令。网上教程一搜一大把但大多是把docker --help翻译一遍看完记不住用起来还是懵。这篇文章不搞那种词典式的罗列我把日常用得最狠、频次最高、以及最容易踩坑的命令重新梳理一遍按使用场景组织每个命令都说说它解决什么问题以及为什么用这个参数而不用别的。内容基于我在实际项目里的操作经验适合刚上手 Docker 不久、想从“会用”进阶到“用得顺手”的朋友也适合已经写了一阵子 Dockerfile 但总感觉命令体系有点散的人。看完你可以直接照着敲把这一套固化下来日常开发部署基本够了。1. 镜像与容器的“生命周期”先把命令体系和底层关系理顺很多人头疼记不住命令本质上是没把 Docker 的几个核心对象的关系理清楚。镜像Image、容器Container、数据卷Volume、网络Network这四样东西构成了日常操作的全部对象。镜像是一个只读模板容器是镜像运行后生成的实例数据卷负责数据独立存储网络解决容器间通信。这个逻辑理顺了命令就成体系了。镜像层面日常最高频的就是docker pull、docker images、docker rmi和docker build。容器层面就是docker run、docker ps、docker start/stop/restart、docker rm、docker exec、docker logs。这套组合拳能覆盖 80% 的日常工作。实际操作里有一个特别容易绕晕的点docker run和docker start到底什么区别。我用一句话总结run create start是新建容器并启动start是启动一个已经存在的容器这个容器要么是之前停了要么是利用docker create单独创建的。后者极少用但要理解两件事是分开的。另外docker ps默认只显示运行中的容器想看到所有容器包括已经退出的必须加-a这也是初学者第一个容易栽的坑。docker rm只能删已经停止的容器运行中的要用docker rm -f强制删但生产环境我建议养成“先 stop 再 rm”的习惯直接-f省事可是会给业务一个不优雅的收尾。之前维护一个支付回调服务某次线上操作图省事直接rm -f数据库连接池里的连接瞬间全断对端重试机制又不完善造成了几分钟的事务超时。从那以后凡是涉及有状态服务的容器我从来不用-f一定先stop等个几秒再rm。这个习惯建议你也有。再说一个高层级的概念容器生命周期内数据默认无法持久化。容器没了它内部文件系统里写入的东西日志、临时文件、数据库数据也一起没了。这就自然引出了数据卷的需求后面专门展开。2. 镜像操作从拉取到构建的完整实操命令镜像这块日常的“增删查改”也有自己的完整链路。docker pull是入口docker images查本地列表docker rmi清理docker build是自制镜像的核心手段。这几个命令单独看都很简单但放在一起用的时候有些细节需要有意训练手感。先把最常用的几个列出来配合真实场景说明docker pull nginx:1.24-alpine拉取指定标签的镜像。alpine版本体积小、依赖少适合作为运行环境。能用alpine就不要用默认的latest否则本地镜像会膨胀得很厉害。我之前一个服务器上光node镜像就占了十几个G全部换成alpine系列之后直接瘦身到不到四个G。docker images查看本地镜像列表。这个命令建议和--digests搭配用能显示镜像摘要确认线上跑的到底是哪次构建的产物。多环境发布时如果镜像 tag 不唯一靠 digest 是最可靠的办法。docker rmi删除镜像。如果镜像被容器引用会直接报错提示你先删容器。这个设计是保护机制防止你删了镜像之后容器启动不了。还有一种“悬空镜像”就是none:none那是重复构建时老镜像失去 tag 的产物可以用docker image prune一把清掉。我有个习惯每次连续构建完一组镜像后会顺手跑一次docker image prune -f释放掉中间层产生的悬空镜像服务器磁盘压力会小很多。构建相关的命令放一块说因为build是最想让人吐槽的环节但日常用法有固定的套路。先看一个最常见的场景你在项目目录下写好 Dockerfile执行docker build -t myapp:1.0.2 .-t指定镜像名和 tag.表示使用当前目录作为构建上下文。很多新手在这理容易误会docker build不是只照着 Dockerfile 干活它会先把整个目录打包发送给 Docker 守护进程然后由守护进程在本地构建引擎中执行 Dockerfile 指令。如果目录里有node_modules这种巨型目录就算 Dockerfile 里没用它也一样会被完整发送一遍构建过程就会变得异常慢。遇到这种问题有两个方案。第一在项目根目录放一个.dockerignore文件把node_modules、build、dist、.git这种不需要进入上下文的目录统统忽略掉node_modules dist build .git *.log .DS_Store第二老版本的构建方式docker build是默认引擎新版本推荐用docker buildx build加 BuildKit 引擎提速效果明显尤其是并行处理多个构建阶段时。Docker Desktop 和较新版本的 Docker Engine 已经默认启用 BuildKit 了所以直接写docker buildx一般没问题。实际项目里如果有多阶段构建需求Dockerfile 里会频繁使用FROM xxx AS builder这种层级的写法这时候构建缓存的作用会非常明显。日常操作上养成一个习惯除非 Dockerfile 有根本性变更只改源代码重新构建用docker build --cache-from或者直接利用默认缓存构建速度会快一个量级。甚至在你确定依赖没有变更时可以考虑docker build --no-cache强制跳过缓存做一次“干净构建”但这会比较慢通常只在 Dockerfile 或依赖版本出现异常时才用。镜像的导入导出也是一个冷门但关键的命令对docker save -o myapp.tar myapp:1.0.2 docker load -i myapp.tar内网部署、离线环境、或者要把镜像完整迁移到另一台服务器时靠这两条命令。save是打包load是导入。需要注意docker save打包的是整个镜像含全部分层和元数据而docker export导出的是容器根文件系统二者不要混。迁移镜像只认save/load这套组合。3. 容器操作run、ps、exec、logs 的高频用法与参数详细拆解容器层面的命令是日常最高频的基石。先把docker run的参数组合讲透因为它一个命令承载了镜像运行的全部配置端口映射、数据卷挂载、环境变量、网络模式、重启策略。这些参数混合在一起只有拆开逐个理解才能不看--help情况下直接写出可用命令。一个最常见的运行命令长这样docker run -d --name nginx-app \ -p 8080:80 \ -v /data/html:/usr/share/nginx/html:ro \ -e TZAsia/Shanghai \ --restartalways \ nginx:1.24-alpine逐项拆开讲-d后台运行容器不占用当前终端。调试阶段不要用-d直接用-it前台运行日志直接打在终端上方便观察。--name给容器起名比如nginx-app。起名很重要后面exec、logs、stop、rm都可以直接用名字不用查容器 ID效率高很多。-p 8080:80端口映射宿主机端口:容器端口。注意顺序左边是宿主机右边是容器内端口方向搞反了就会发生请求找不到端口的问题。同一个宿主机端口不能同时映射给两个容器。-v /data/html:/usr/share/nginx/html:ro目录挂载把宿主机目录映射到容器目录。:ro表示只读防止容器内进程篡改宿主机文件。这个设计在生产环境非常实用一份静态资源同时挂给多个容器时只读权限能防误操作。-e TZAsia/Shanghai环境变量。时区问题是最常见的一种坑基础镜像默认多是 UTC 时区容器内时间跟北京时间差八小时。只要不是对时区有特殊要求的场景启动时一律带上这个变量。--restartalways容器退出后自动重启顺势覆盖了服务器重启后容器自动拉起的需求。这是生产环境的基础设定。灵活度高一点用unless-stopped它的区别是你自己手动 stop 过的容器下次服务器重启时不会自动拉起而always会。我个人偏向unless-stopped避免杀了一个维护中的容器重启机器后又自己跑起来。容器列表查询也有讲究docker ps显示运行中的容器docker ps -a显示所有容器包括已停止的。这个-a参数非常常用。调试时容器因为错误退出docker ps里看不到会让人困惑为什么服务没起来。这时候第一反应就是docker ps -a找到那个 Exited 状态的容器再结合docker logs排查。给ps加--format可以定制输出列我习惯用一行精简格式docker ps -a --format table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}这样输出更紧凑容器状态和端口映射一览无余。脚本里要拿容器 ID 或状态时这种格式配合awk是最顺手的方案比如docker ps -a --format {{.ID}} {{.Names}} | grep nginx容器运行时进入内部排查问题靠docker execdocker exec -it nginx-app /bin/sh-i保持标准输入打开-t分配一个伪终端。两个参数组合使用才能得到一个可交互的 shell。如果是 Alpine 基础镜像只有/bin/sh没有 bash直接写/bin/bash会报错找不到文件。这是新手常踩的坑看到 bash 报错先别慌换成 sh 就好。docker logs是排查容器日志的入口最常用的几种用法docker logs nginx-app # 查看全部日志 docker logs --tail 100 nginx-app # 看最近100行 docker logs -f nginx-app # 实时跟踪日志类似 tail -f docker logs --since 30m nginx-app # 看过去30分钟的日志docker logs看不到日志通常不是命令的问题而是应用把日志写到了容器内的文件里而没有输出到 stdout。容器里跑 Node 或者 Python 应用要把日志用 console 方式输出到标准输出Docker 日志机制才能采集到。如果应用已经写进了文件硬要看的话只能exec进去用tail看容器内文件。容器清理是很容易被忽略但必须做的一件事docker rm只能删停止的容器docker rm -f能强删运行中的容器。批量清理停止的容器docker container prune -f这个命令会删除所有处于停止状态的容器。如果只想删一条docker rm 容器名。批量删除指定名称匹配的容器可以配合 filterdocker rm $(docker ps -aq --filter statusexited)注意$(...)内层列出所有 Exited 状态的容器 ID外层依次删除。这个组合在清理积压容器时非常好使我几乎每周都会跑一次。4. 数据卷与网络管理容器持久化和互通的关键操作容器销毁后数据不丢的秘密全在数据卷。Docker 提供了三种挂载方式bind mount、named volume、tmpfs。日常里最常见的两种操作是 bind mount 和 named volume这两个的区别和适用场景需要真正理解。bind mount 就是上面run命令里的-v /宿主机路径:/容器路径比较适合开发阶段实时同步代码、配置文件或者把日志写到宿主机指定目录。但生产环境有个问题宿主机目录依赖具体机器路径迁移、备份都要额外处理。named volume 的做法是让 Docker 管理数据目录docker volume create appdata docker run -d --name pg-db -v appdata:/var/lib/postgresql/data postgres:15这里appdata是卷名Docker 会自动在它的数据目录下创建对应路径保存到/var/lib/docker/volumes/appdata/_data。但注意数据卷在容器删除后默认不会自动删除这让数据可以在容器重建后继续保留也正是我们要的持久化效果。管理数据卷有三个高频命令docker volume ls查看所有卷。docker volume rm 卷名删除卷但会拒绝删除被容器引用的卷。docker volume prune删除所有未被任何容器引用的卷这个命令会释放磁盘空间但加-f前先确认没有需要保留的卷。我处理过一次prune把旧版本遗留的 PostgreSQL 数据卷清掉的场景那次直接导致恢复困难教训非常深刻。备份数据卷的正确思路容器还在跑没关系直接用 tar 打包卷目录。比如备份 appdata 卷docker run --rm -v appdata:/data -v $(pwd):/backup alpine tar czf /backup/appdata.tar.gz -C /data .这个命令临时创建了一个 Alpine 容器把卷挂载到/data把当前目录挂载到/backup然后在容器内执行tar打包。执行完容器自动删除--rm宿主机当前目录下就生成了备份文件。这个方法不需要停业务是我用过最顺手的卷备份姿势。网络部分Docker 的网络模型日常接触最多的是 bridge 网络。默认情况下所有容器跑在名为bridge的默认桥接网络中。容器之间可以通过 IP 互通但 IP 会漂移靠 IP 不好使最可靠的方式是自定义网络然后用容器名当作主机名解析。自定义网络的操作步骤docker network create app-network启动容器时指定网络docker run -d --name nginx-app --network app-network nginx:1.24-alpine docker run -d --name api-server --network app-network myapp:1.0.2这样配置之后api-server容器内直接用http://nginx-app:80就能访问到nginx-app容器。这个机制是 Docker 内置的 DNS 解析在自定义网络下自动生效不用额外配置。我日常拉一个多服务项目时一定会把所有服务全部放进同一个自定义网络避免用--link这种老掉牙的关联方式。端口映射与网络的配合也需要注意。如果一个容器只在自定义网络里服务其他容器其实没有必要做-p端口映射。比如上面api-server只需要被 nginx 反向代理访问它本身不需要对外暴露端口只要两个容器在同一个网络里就够了。反过来如果你忘了把容器加入同一网络却在排查为什么从 A 容器访问 B 容器失败这时候八成是网络隔离造成的。还有一个常用命令是查看容器的 IP 和网络信息docker inspect nginx-app | grep IPAddressdocker inspect --format {{.NetworkSettings.Networks.app-network.IPAddress}} nginx-appinspect是万能信息查询工具容器配置、挂载、网络、环境变量、状态都能查。调试时可以多用但别依赖 grep 大海捞针用--format直接提取才高效。5. 清理、监控与调试docker stats、system df 等容器日常维护命令服务器跑得久了Docker 占磁盘空间的问题总是会冒出来。Docker 的磁盘占用大头主要是镜像、容器可写层、日志文件、数据卷以及构建缓存。这里有一套日常巡检的组合命令。先看空间用量docker system df这个命令一跑出来镜像、容器、本地卷、构建缓存的大小一目了然。它比df -h更精确直接展示了 Docker 自己管理的那些目录占了多大空间。最底下那行Reclaimable显示可回收的空间有多大配合下一步清理动作。清理空间的标准操作docker system prune默认只清理停止的容器、无用的网络、悬空镜像和构建缓存。想连未被引用的数据卷也清理掉加-adocker system prune -a但这里我要重点提醒-a会顺带把所有未被容器引用的镜像全删了包括那些你曾经构建但当前没有容器运行的版本。如果生产环境没有成熟的镜像仓库做缓冲本地只剩一份镜像版本千万慎用-a。实际操作里我通常只用不带-a的system prune再单独用docker image prune和docker volume prune分目标清理每一步都确认过目标再执行。容器资源占用监控这块docker stats是我每天必敲的命令docker stats [容器名]它会实时展示容器的 CPU 使用率、内存占用、网络 I/O、磁盘 I/O 和进程数。像看任务管理器一样直观。部署完成后我会先连着跑几分钟docker stats确认资源占用没有异常毛刺再收工。还有一个调试场景很常见某个容器一直重启或者启动几秒就退出这种情况怎么排查我的固定套路docker ps -a找到容器名和退出状态。docker logs 容器名看应用崩溃日志。docker inspect 容器名看退出码和错误信息重点看State字段里的ExitCode和Error字段。比如ExitCode 1通常是应用启动时报错ExitCode 137多半是容器被 OOM Killer 杀了内存配置不够ExitCode 139是段错误常和底层库兼容性问题有关。这些退出码对应的含义是排查的第一线索我踩过一次非常郁闷的坑服务莫名其妙退出且无任何日志最后docker inspect一看是OOMKilled为 true才知道是内存上限设得不够。实时查看容器内进程也能辅助排查docker top 容器名类似宿主机top命令显示容器内的进程列表。当你怀疑容器里是不是起了一个异常进程或者应用进程莫名退出时先用这个命令确认内部状态。容器内文件拷进拷出也是运维环节的高频操作docker cp ./local-file.txt 容器名:/data/ docker cp 容器名:/data/log.txt ./local-backup/docker cp的好处是不需要进入容器直接宿主机操作文件。传配置文件、导出日志、备份小文件都很好用。但不建议把docker cp当成文件同步工具用大文件和目录结构建议走数据卷挂载效率更高。6. 高效进阶run 命令的隐藏参数与日常脚本化的操作习惯看到这里常规操作基本覆盖完了。但日常操作能不能再高效一点完全取决于能不能把 Docker 命令和真实工作流结合起来。我个人靠三件套提升效率尽可能给容器都打上--name、为命令写简短的 shell 脚本、把一键启动脚本化。先讲--name。很多人用 Docker 图省事启动时不写名字后面查找全靠docker ps里的 Container ID 前几位。容器数量一多这种操作会让人抓狂。跑容器务必定名docker run -d --name myapp-2024.11 ...名字起得有规律后面exec、logs、restart全部用名操作等于给每个容器一个好记的句柄。配合docker rename可以随时改名但其实起名时想好最省事。再说一个高频参数--env-file。如果是运行配置项特别多的容器不要在一个超长的命令行里塞十几个-e写成 env 文件docker run -d --name api-server --env-file .env.prod myapp:1.0.2.env.prod文件里一行一个KEYVALUEDB_HOSTpg-db DB_PORT5432 DB_NAMEappdb TZAsia/Shanghai这样生成的 run 命令非常短易维护换环境部署时只要换 env 文件。我从这个习惯里受益最多的一次是在某客户现场批量部署了三个环境配置各不相同的同业务容器全靠 env 文件切换效率提升了一个量级。最后说下日常脚本化。我在本地维护了一个docker-helper.sh脚本把最常用的一套操作封装成短命令起来大概是这个感觉#!/bin/bash # docker-helper.sh alias dpsdocker ps --format table {{.Names}}\t{{.Status}}\t{{.Ports}} alias dlogsdocker logs --tail 100 -f alias dstopalldocker stop $(docker ps -q) alias dcleandocker system prune -fdlog直接跟进最近 100 行日志dstopall一键停止所有容器dclean快速清理无用资源。这套脚本不复杂但确确实实降低了日常操作的心智负担。还有一个容易忘但生命周期很短的高频操作叫“临时一次性容器”。比如临时想看某个目录的文件结构或者临时连数据库却没装客户端docker run --rm -it -v $(pwd):/work alpine sh--rm让容器退出后自动删除不会留下垃圾容器-v $(pwd):/work把当前目录带进容器。用完退出自动清理不污染环境。这个命令是我做临时调试的万能钥匙什么场景都适用。7. 日常必踩的坑换镜像仓库、时区、存储、权限等高频问题实录这节把我在实际运维和日常开发里反复踩过、且新手最容易中招的坑汇总在一起做成一个快速排查清单。这些问题我几乎每个月都会在社区里看到有人在问用一篇的篇幅集中讲透。先说镜像拉取相关的问题。docker pull在某些网络环境里慢得无法忍受或者直接超时最直接的办法是给 Docker 配置镜像加速器。但这个得看运行环境建议把官方配置和第三方加速源对比后再选。配置不复杂关键在于确认镜像加速器可用然后重启 Docker 服务生效。如果加速后还是慢需要检查是不是在构建阶段而不是 pull 阶段慢的区别就在于构建时如果有apt-get install这类指令瓶颈可能是系统包源而不是 Docker。用基础镜像自带国内源的过程往往比单纯调 Docker 配置更能解决问题。第二个重灾区是容器内时区。默认基础镜像时区是 UTC导致日志时间和业务时间差八个小时。解决方案有两个启动时用-e TZAsia/Shanghai设置环境变量或者进入容器后手动改/etc/localtime。但后一种方式在容器重建后失效所以规规矩矩在启动命令里加环境变量才是正解。有些基础镜像需要安装tzdata包才能正确识别时区这种属于基础镜像本身精简的问题遇到就在 Dockerfile 里补上RUN apk add --no-cache tzdata cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime第三个坑是存储驱动和磁盘爆满。容器运行期间会产生大量日志Docker 默认的 json-file 日志驱动会持续累积日志文件且不自动轮转。一个高并发服务跑一个月日志文件撑到几十 G 是常事。最省事的规避办法是在/etc/docker/daemon.json里配置日志轮转{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这样单份日志最大 10M保留 3 份超了自动滚动清理。修改完要重启 Docker 服务。但注意这只对新创建的容器生效已存在的容器需要重建才能应用新的日志轮转设置。日常巡检时可以定期用du -sh /var/lib/docker/containers/*看看哪些容器的日志目录特别大主动清理异常容器。第四个坑是 Docker 守护进程的权限问题。Linux 环境下普通用户直接敲docker ps会提示没有权限访问/var/run/docker.sock。解决办法分两种要么把用户加入 docker 用户组sudo usermod -aG docker $USER要么每条命令加sudo。我建议用用户组的方式但需要知道一个安全代价加入 docker 用户组等价于拿到宿主机的 root 级权限因为容器可以挂载宿主机任意目录。所以生产服务器上我依然坚持用sudo个人开发机才用用户组方案。第五个绕不开的是容器内文件权限问题。数据卷挂载后容器内进程对挂载目录的读写权限经常和宿主机不一致表现就是报Permission denied。需要知道容器进程的身份很多基础镜像默认以 root 运行但一些官方镜像会切换到低权限用户。遇到权限问题先确认容器内 uid用docker exec 容器名 id查看然后处理宿主机目录权限或者通过参数指定用户方式让容器以相同 uid 运行。这里要特别注意完全没有必要看到权限问题就直接 chmod 777那是把安全隐患挡在门外的方式。8. 最后的实操心得这些年用 Docker最大的感悟不是背下多少命令而是建立一套自己的操作直觉。看到一个新服务第一反应是它是无状态的还是需要持久化需要暴露端口给外部吗是不是要在内网和其他容器互联日志是打到标准输出还是文件把这些想清楚了docker run的参数组合自然就出来了。我个人的习惯是每次搭建环境都优先加上--name统一时间-e TZAsia/Shanghai日志量大的服务提前配好 json-file 轮转凡是数据库和文件存储类的目录全部走 named volume。这套组合在日常项目里几乎不变式使用既稳定又省心。如果非要选一个“最有用的小习惯”我会选“容器停用后过段时间用docker system df看一遍空间”这能避免绝大多数磁盘爆满的意外。这招救过我太多次了。
阅读完成 · 觉得有帮助?