
镜像名、容器名这些基础概念如果你已经搞清楚了那日常在终端里敲得最多的其实就是那几十个docker命令。这篇东西不聊架构原理也不扯编排和集群就专注在单机环境下的日常操作命令镜像怎么拉、容器怎么起、日志怎么看、数据怎么拷、网络怎么配以及我实际用下来踩过哪些坑。适合刚接触docker没多久、想在终端里操作更顺手的人也适合平时命令用得比较杂、想系统梳理一遍的开发者。1. 日常命令的认知框架先弄清楚docker命令的底层逻辑很多人学docker命令是零散记忆的用到一个查一个今天记了明天忘。我一开始也是这样后来把这些命令按“对象动作”的方式重新梳理了一遍发现其实特别有规律。docker的命令体系本质上是一套典型的客户端-服务端结构。终端里敲的docker命令是客户端它通过socket或者HTTP接口跟后台的dockerd守护进程通信。所以你敲一个docker ps终端显示出来的容器列表并不是客户端自己编出来的而是客户端问dockerd要来的。这一点理解之后很多异常现象就比较容易想通比如明明容器在跑但docker ps看不到大概率是容器不在当前namespace下或者你连的docker上下文不对。日常命令围绕的核心对象就三类镜像、容器、数据卷。外加网络、仓库、日志这些辅助对象。每类对象都有三个基本动作查看、创建、删除。把这套“三乘三”的骨架记住再往里面填具体的命令就很快了。对象查看创建/运行删除/清理镜像docker imagesdocker pull / builddocker rmi容器docker psdocker run / createdocker rm数据卷docker volume lsdocker volume createdocker volume rm还有一个重要的认知docker命令的很多参数是“全局通用”的比如-v挂载目录、-e设置环境变量、--name指定容器名这些在run、create等命令里都适用。把常用参数吃透要比死记命令本身性价比高得多。我后面会反复用到这些参数所以建议你先在脑子里留个印象。2. 镜像操作从拉取到清理的完整实践2.1 拉取镜像时容易忽略的几个细节镜像拉取是Docker日常最高频的操作。docker pull nginx这种基础用法没什么好说的但我实际用下来有几个点值得单独提一下。首先是指定标签的问题。很多人直接docker pull nginx这会默认拉latest标签而latest其实是不确定版本。今天拉的latest跟上周拉的latest很可能是两个不同的版本。我在生产环境吃过一次亏某天重新部署拉下来的镜像版本变了行为也变了排查了半天才发现是latest漂移导致的。所以建议不管什么场景养成指定标签的习惯docker pull nginx:1.25.3或者至少用nginx:1.25这种大版本标签。测试环境无所谓但跟线上相关的一定要指定。其次是架构问题。如果你用的是ARM架构的机器比如Apple Silicon芯片的笔记本直接docker pull默认拉取的是ARM版本镜像。但有些老镜像没有ARM版本这时候docker会自动尝试用模拟运行或者直接报no matching manifest for linux/arm64的错误。解决办法是主动指定平台docker pull --platform linux/amd64 nginx。这个参数我在实际工作中用得不少尤其是做跨平台测试的时候。还有一个很多人不知道的点docker pull支持同时拉多个镜像。docker pull nginx:1.25 redis:7.2这样写在一行它就会按顺序逐个拉取。适用于批量初始化一个新环境。2.2 镜像信息查看与磁盘占用分析docker images的输出看起来简单但里面有几个信息值得关注IMAGE ID是镜像的唯一标识实际是sha256摘要的前12位SIZE显示的是镜像的“压缩后逻辑大小”注意这跟实际占用的磁盘空间不一定相等因为镜像层是有共享的。日常排查磁盘空间的时候docker system df比docker images更直观。它会统计镜像、容器、数据卷、构建缓存分别占了多少空间最后一列RECLAIMABLE还告诉你哪些空间是可以回收的。我一般在告警磁盘空间不足时先跑这个命令看下大头在哪一块再决定清理策略。docker inspect是用来查看镜像和容器详细信息的万能命令。比如我想知道某个镜像暴露了哪些端口docker inspect nginx:1.25 --format {{.Config.ExposedPorts}}--format配合Go模板非常强大日常查询完全可以替代掉漫长的JSON输出。比如查看镜像入口点docker inspect nginx:1.25 --format {{.Config.Entrypoint}}查看容器的IP地址也是这个命令的常见用途docker inspect 容器名 --format {{.NetworkSettings.IPAddress}}2.3 镜像清理的正确姿势与标签管理镜像清理是docker日常运维里最容易翻车的地方。docker rmi 镜像名:标签删除的是指定标签的镜像但如果你删的是latest这种被多个容器引用的标签可能会把其他容器正在用的镜像层也删掉导致容器启动报错。安全做法是先停掉相关容器确认没有容器依赖这个镜像了再执行删除。docker rm删容器、docker rmi删镜像这个顺序不能反。批量清理时docker image prune可以清理所有“悬挂镜像”——就是那些没有标签、没有被任何容器引用的镜像层残留通常是构建过程中产生的中间层。我一般定期执行docker image prune -f如果空间确实很紧张也可以加上-a参数把没有容器使用的所有镜像都清掉但操作前一定要看清楚别把准备回滚用的旧版本镜像给误删了。镜像标签有个冷门但实用的操作docker tag可以给已有镜像打新标签。比如我从仓库拉了一个myapp:20240101想把它当作myapp:latest来用不需要重新拉取docker tag myapp:20240101 myapp:latest这个命令在版本回滚时特别好用直接重新打个标签就能让已有的部署脚本指向老版本。3. 容器生命周期从创建到销毁的完整链路3.1 创建与启动容器run命令的参数组合docker run是Docker里最核心、最复杂的命令没有之一。它本质上是“创建容器并启动”的组合相当于是docker create加docker start。我建议新手先在脑子里拆开理解这两步这样后面排查问题时会清楚很多——比如容器启动失败到底是创建阶段就出错了还是start阶段应用崩溃了。日常写docker run其实就是从一堆参数里挑你需要的组合。我最常用的一组是docker run -d \ --name myapp \ -p 8080:80 \ -v /home/user/data:/data \ -e MYSQL_HOST127.0.0.1 \ --restart unless-stopped \ myapp:1.0.0逐个解释一下-d是后台运行不加的话会前台运行日志会直接打到终端上退出终端容器就停了--name给容器取名字不取的话docker会生成一个随机名比如hungry_mclean这种-p 8080:80是端口映射宿主机8080端口转发到容器的80端口-v挂载数据目录宿主机路径在前容器路径在后用冒号分隔-e设置环境变量这个在配置数据库连接、API密钥时特别常用--restart unless-stopped是设置自动重启策略docker守护进程启动时会自动拉起这个容器只有手动stop的情况下才不会重启。关于--restart策略我多说两句。它有四个选项no默认不自动重启、on-failure异常退出才重启、always总是重启、unless-stopped除手动停止外总是重启。我个人的习惯是生产环境的服务都用unless-stopped因为always有个坑——即使你手动stop了容器dockerd重启后它还是会自动拉起来有时候会干扰你在维护窗口内的操作。-p参数其实有两种写法不懂的人容易搞混。-p 8080:80是讲“宿主机的8080端口映射到容器的80端口”冒号左边是宿主机右边是容器。如果你只写-p 80docker会随机找一个宿主机端口来映射到容器的80端口。我建议始终写完整的映射关系方便后面维护。3.2 容器启停与删除的状态转换细节容器的生命周期状态转换其实比大多数人想的要复杂一点。每个容器的状态永远是以下之一created刚创建未启动、running运行中、exited已退出、paused已暂停、dead异常状态无法正常停止。对应的命令是docker start 容器名 # 启动一个已存在的容器 docker stop 容器名 # 优雅停止默认等10秒 docker kill 容器名 # 强制停止紧接着就发SIGKILL docker restart 容器名 # 重启等价于stop加start docker pause 容器名 # 暂停进程还在但不再分配CPU docker unpause 容器名 # 恢复暂停的容器docker stop和docker kill的区别值得细说。stop命令发的是SIGTERM信号相当于告诉容器里的主进程“准备一下我要停了”给它一个善后的时间比如保存数据、关闭连接等10秒之后如果还没退才会发SIGKILL强制杀。kill则是直接发SIGKILL没有协商余地。在线上环境如果应用本身就写得比较规范我用得最多的是docker stop。另外注意区分docker rm和docker stop的关系stop只是停止运行容器还在还能再startrm是彻底删除容器包括它的读写层删除后就不能再使用了之前对容器内文件的修改也会丢失。批量操作容器时配合过滤条件很好用。比如删除所有已停止的容器docker container prune -f删除所有容器包括运行中的但很危险谨慎用docker rm -f $(docker ps -aq)3.3 进入容器的两种主要方式对比进入一个正在运行的容器最常用的是docker exec和docker attach。两者的定位完全不同docker exec -it 容器名 bash是在容器里启动一个新进程执行指定的命令。-i保持标准输入打开-t分配一个伪终端两个参数配合起来你才能正常交互。docker attach 容器名是连接到容器主进程的输入输出上看到的是主进程的输出CtrlC可能会直接给主进程发SIGINT导致容器退出。我日常几乎只用docker exec因为它是“新开一个会话”的模式适合执行临时命令、排查问题。调试时经常这样操作docker exec -it myapp bash如果容器里没有bash有些精简镜像只有sh就换成docker exec -it myapp sh还有一种常见场景是传环境变量进去执行一次性命令比如在容器里跑一个数据库迁移脚本docker exec -it myapp python manage.py migrate记住exec进容器里改的东西只在容器存活期间有效。容器一旦被删除重建所有修改都会消失。所以任何需要持久化的改动都要考虑写进Dockerfile重新构建或者挂载数据卷。4. 运行观测日志、进程与资源监控4.1 日志查看的实用技巧docker logs是排查容器问题的第一入口。它的基础用法是看全部日志docker logs myapp但实际场景里全量日志往往太长用得最多的是这几个组合docker logs -f myapp # 实时跟踪日志相当于tail -f docker logs --tail 100 myapp # 只看最后100行 docker logs --since 10m myapp # 只看最近10分钟的日志 docker logs --until 2024-01-01T00:00:00 myapp # 只看某时间点之前的--since和--until我用的比较多的是排查“刚才发生了什么事”你大概知道时间窗口就能精准定位。这里有个关键点docker logs显示的是容器的标准输出stdout和标准错误stderr也就是进程直接往控制台打印的内容。如果你的应用把日志写进了文件而没有输出到stdout那docker logs是看不到的。很多应用默认配置就是写文件在容器化部署时需要把日志输出改为stdout这是容器化改造的一个常见注意点。另一个细节是日志驱动。docker默认的日志驱动是json-file日志文件会落在宿主机上默认大小限制是单文件64KB实际上默认没有上限这会导致一个严重问题跑了几年的容器日志文件可能膨胀到几十个GB把磁盘塞满。解决办法是启动容器时加参数docker run -d --log-opt max-size10m --log-opt max-file3 myappmax-size10m表示单个日志文件最大10MBmax-file3表示最多保留3个文件超过就轮转删除。这个参数在Docker部署里建议都加上。4.2 容器内进程与资源占用监控要看容器里实际跑了哪些进程用docker top输出格式跟Linux的top命令类似docker top myapp如果需要实时看CPU、内存等资源占用docker stats是更直观的工具docker stats它会持续刷新每个运行中容器的CPU占用率、内存占用、网络IO、磁盘IO。默认是无限刷新加--no-stream可以只输出一次就退出便于写脚本采集docker stats --no-stream我一般用docker stats做快速体检看看哪个容器在偷偷吃内存。如果一个容器内存持续上涨不回落大概率是内存泄漏需要考虑加--memory限制参数了。限制容器资源也可以在run时直接指定docker run -d --memory512m --cpus1.0 myapp--memory512m限制最大内存--cpus1.0限制最多使用一个CPU核心。这个在生产环境非常关键不然某个容器内存溢出可能把整台机器拖垮。4.3 容器事件监控docker events是一个比较冷门但很实用的命令。它会实时输出docker守护进程的各种事件比如容器启动、停止、销毁、镜像拉取等docker events --filter containermyapp加--filter可以只监控特定对象。这个命令调试自动化脚本时很好用——你在另一个终端跑部署这边盯着事件流能看清整个执行流程中docker在后台做了什么操作。配合--since参数还能回看历史事件docker events --since 1h虽然多数人日常用不上但这个命令一旦遇到“容器为什么莫名其妙重启”这类问题时价值就体现出来了。5. 数据持久化与容器内外文件传输5.1 数据卷与挂载的日常管理容器本质上是临时的删除重建后里面的数据就没了。为了持久化数据docker提供了两种方式绑定挂载bind mount和数据卷volume。绑定挂载就是-v /宿主机路径:/容器路径直接把宿主机的目录挂进容器。数据卷是docker管理的存储空间通过docker volume系列命令操作docker volume create mydata docker volume ls docker volume inspect mydata使用数据卷启动容器docker run -d -v mydata:/app/data myapp绑定挂载和数据卷的选择我的经验是开发环境图省事直接用绑定挂载改代码立刻生效生产环境的数据尤其是数据库这种我倾向于用数据卷因为它有docker统一管理备份和迁移更方便。查看容器的挂载信息docker inspect 容器名 --format {{json .Mounts}}5.2 容器内外文件拷贝容器内外传文件用docker cp。这个命令的方向是双向的# 宿主机文件拷贝到容器内 docker cp ./local.conf myapp:/etc/app/config.conf # 容器内文件拷贝到宿主机 docker cp myapp:/var/log/app.log ./app.logdocker cp支持目录拷贝而且会保留文件权限。注意它不需要容器是运行状态只要容器存在哪怕是exited状态就能拷贝。这个特性在容器已经崩溃、需要抢救里面数据时特别有用。5.3 commit操作的正确使用场景docker commit是把当前容器保存为一个新镜像。比如你手动在容器里装了某个工具、改了某个配置想保留下来docker commit myapp myapp:v2-with-tools这个命令能用但要清楚它的局限性。commit出来的镜像会包含容器运行期间产生的所有文件变更包括临时文件、缓存、甚至密码环境变量不推荐作为镜像构建的常规手段。我就见过有人靠commit维护“手工镜像”最后镜像体积膨胀得离谱而且构建过程完全不可复现。正确做法还是写Dockerfile从基础镜像一步步构建。commit只适合紧急情况下的“快速保存现场”比如容器里做了大量临时调试怕后面丢了先commit一个备份再说。6. 网络配置与容器互通6.1 端口映射的检查与调整查看容器端口映射情况docker ps输出里的PORTS列就是更详细的信息用docker portdocker port myapp它会输出类似80/tcp - 0.0.0.0:8080这说明容器的80端口被映射到了宿主机的8080端口。已经运行的容器没法直接改端口映射除非把容器删了重新run。所以设计端口映射时要想清楚我一般在run之前就确认好宿主机端口跟容器端口是否冲突、是否要绑定到特定IP比如只允许内网访问就绑定127.0.0.1:8080:80。6.2 自定义网络与容器间通信同一台机器上的多个docker容器之间互访有两个方式用默认的bridge网络通过IP互访或者加入同一个自定义网络通过容器名互访。默认bridge网络下容器IP是不固定的重建之后就变了靠IP通信非常脆弱。解决办法是创建自定义bridge网络docker network create mynet然后在run时指定网络docker run -d --name app1 --network mynet myapp:1.0 docker run -d --name app2 --network mynet myapp:1.0这样在app1容器里就可以直接用app2这个容器名访问app2docker exec -it app1 curl http://app2:8080docker的自带DNS服务会自动解析容器名。这是单机多容器部署时的标准做法比写死IP优雅得多。查看网络信息docker network ls docker network inspect mynet清理不再使用的网络docker network prune -f7. 日常高频问题排查手册7.1 常见错误与解决我整理了几个日常最常遇到的docker命令问题做成速查表方便你排查。错误现象可能原因解决办法port is already allocated宿主机端口被占用换一个宿主机端口ss -lntp查看占用进程docker: permission denied当前用户不在docker组sudo usermod -aG docker $USER后重新登录No space left on device磁盘被镜像或日志撑满docker system df排查docker system prune -f清理container name already in use同名容器已存在docker rm -f 同名容器或改容器名exec failed: no such file or directory镜像中没有指定shell换成sh或安装bash后重试OCI runtime exec failed容器已经退出先检查容器状态用logs看退出原因Get [...] connection refuseddockerd没有启动systemctl start docker或service docker start最常被问的是docker ps没有任何输出但系统明显在跑服务。这种十有八九是你连的docker对象不对——比如本机装了多个docker环境或者设置了DOCKER_HOST环境变量指向了其他地方。排查思路先echo $DOCKER_HOST看看有没有特殊值再docker context ls看当前上下文指向哪里。7.2 快速排查容器问题的通用思路容器起不来不要急着上网搜报错。我自己的排查顺序是docker ps -a看容器当前状态。exited的话看退出代码是多少0表示正常退出非0就是程序异常。docker logs --tail 50 容器名看最后的日志。通常启动失败的报错就在这里。docker inspect 容器名看配置有没有问题挂载路径、环境变量、端口是否都正确。如果还是看不出来docker run加-it前台运行一次日志直接打出来比后台模式更容易定位。这套流程能解决掉日常95%的容器启动问题。剩下的5%一般出在镜像本身——换一个相似版本试试或者直接看镜像的启动命令是否适合你当前的环境。8. 我日常积累的几个命令习惯最后分享几个我自己的使用习惯虽然不是标准文档里的内容但实际用下来很顺手。一个是给容器统一命名规范。我用项目名-环境-服务的格式比如blog-prod-nginx、blog-dev-mysql。这个习惯让docker ps的输出一目了然也方便写脚本批量操作。名称里尽量避免下划线docker有些地方对下划线处理比较麻烦用连字符更稳妥。另一个是善用docker system df做定期大扫除。每周会在开发机上跑一次docker system prune -f这个命令会清理所有已停止的容器、悬挂镜像、无用网络和构建缓存。已经停止的容器占不了多少空间但悬挂镜像和build cache可能占好几个GB清完常有惊喜。还有一个技巧是记录常用命令。我会在项目里放一个Makefile把常用的docker操作用变量封装起来比如up: docker compose up -d down: docker compose down logs: docker logs -f app配合make的自动补全每天能省不少敲命令的时间。虽然这是compose的内容但在日常单容器操作中把长命令封装成短命令的思路也值得借鉴——比如把上面那些复杂的docker run参数组合存成一个shell脚本下次直接执行脚本就能重建一个一模一样的容器这就是最朴素的“基础设施即代码”。最后一个习惯跟安全相关不要没事干就在容器里跑--privileged特权模式。这只是开发测试偶尔需要的能力普通容器根本用不到。一旦开了特权模式容器里的进程就有了宿主机的完整权限相当于门户大开实在没必要冒这个险。Docker日常命令这东西本质上不用背用得多了肌肉记忆就形成了。关键是理解每条命令背后的动作和状态变化知道出了问题该去哪里看。希望这篇梳理能给你省下一些翻文档的时间。