ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Docker命令完全指南:按对象×动作分类,覆盖镜像、网络、卷与Compose实战

Docker命令完全指南:按对象×动作分类,覆盖镜像、网络、卷与Compose实战 别人问Docker命令怎么记我的回答从来都是你先别背命令先搞明白Docker到底有几类“对象”。镜像、容器、网络、数据卷、仓库、宿主机这就是全部家当。所有命令本质上就是docker 对象 动作这么个语序。这篇会把Docker命令按这个逻辑拆开讲覆盖镜像容器的高频操作、Docker Desktop安装失败后的排查、网络不通时怎么一步步定位、数据卷怎么做到删容器数据不丢、Compose怎么编排多容器以及Dockerfile构建时怎么让镜像更小、构建更快。不管你是刚把Docker装上准备跑第一个容器还是已经在项目里用了一段时间但遇到问题总得现查命令这篇都值得看完再动手。1. 先建立命令分类心智比背一百条命令管用1.1 命令记不住是因为没按“对象×动作”拆我观察过不少同事用Docker的状态docker ps和docker run这种天天用闭着眼都会一遇到docker volume、docker network这种出现频率稍低的命令就开始翻历史记录了。问题不在于记忆力而在于没有在脑子里建一个分类索引。Docker命令体系其实特别规整核心就五个对象外加一个宿主机资源维度对象常见动作代表命令镜像 Image拉取、查看、删除、构建、推送pull, images, rmi, build, push容器 Container运行、查看、启停、进入、删除run, ps, start/stop, exec, rm网络 Network创建、查看、连接、断开create, ls, connect, disconnect数据卷 Volume创建、查看、删除、清理create, ls, inspect, prune仓库 Registry登录、拉取、推送login, pull, push宿主机资源资源占用、系统信息stats, info, version你可以把Docker理解成一个“集装箱码头管理系统”。镜像是你从仓库运来的一摞图纸容器是根据图纸造出来的一个个活动房网络是活动房之间的走线和管道数据卷是仓库里专门用来存放图纸修改记录的抽屉。你要操作哪个环节就去调取对应的对象命令而不是把几十条命令放在一个平铺的清单里背。我第一次把命令按对象归类画成一张草图贴在工位后记命令的成本瞬间低了一半。遇到不熟悉的操作时先问自己两个问题我要操作哪个对象我要对这个对象做什么动作答案一组合命令基本就呼之欲出了。1.2 字典式用法任何命令先问 --help很多新手不敢用--help总觉得这像是承认自己不会。恰恰相反--help才是资深用户最常按的命令之一。Docker每个子命令都自带一份相当完整的说明书比你在搜索引擎里搜到的结果往往更精确。# 查看docker全部子命令 docker --help # 查看run子命令的全部参数 docker run --helpdocker run --help出来是一长串选项列表刚接触时会觉得信息量大到消化不了。我的建议是别一整页啃分三次看第一次只看跟容器生命周期相关的-d、-it、--name、--rm第二次只看网络和发布端口相关的-p、--network第三次看存储和资源限制-v、--cpus、--memory。三次看完docker run基本就没有陌生参数了。还有一个容易忽略的用法docker COMMAND --help里有些参数不写在主帮助里而是藏在示例里。比如docker run --help末尾会有几个EXAMPLES平时用-v挂载目录记不清写法时翻一下示例部分比猜语法管用得多。记住一点Docker命令就算你不用它也会把答案放在--help里遇到不确定的第一个动作永远是敲这个而不是百度。1.3 拿Linux命令当参照物记忆成本再降一半Docker命令的设计者明显借鉴了Linux系统命令的肌肉记忆。对已经熟悉Linux基础命令的人来说一大半Docker命令根本不需要重新背docker ps相当于ps查进程容器状态docker logs相当于tail -f看应用日志输出docker top相当于top看容器内进程的实时资源docker exec相当于ssh进入一个“远程主机”执行命令docker inspect相当于cat /proc/xxx看某个对象的细粒度元数据docker port相当于ss -lntp看端口映射关系这个对照表对Windows用户尤其友好。很多从没用过Linux的人第一次敲docker exec -it 容器名 bash时会愣住不知道-it是什么意思。用类比解释就很好懂-i是保持输入流打开-t是分配一个终端合起来就是“开个终端进去敲命令”跟Windows远程桌面的道理一样只不过一个图形界面一个命令行。把命令和已有的知识挂上钩之后剩下要记的其实就只剩几十个参数。参数再多也是围绕着“端口、存储、网络、环境变量、重启策略”这几类需求展开的下面几章我就按这个思路把这些参数和场景串起来讲。2. 镜像与容器高频命令的完整实操链路2.1 镜像的获取、查看与清理每个Docker项目跑起来的第一步几乎都是拉镜像。docker pull的语法简单到没什么好说的但有几个细节会影响后续使用# 不指定tag默认拉latest docker pull nginx # 明确指定版本和变体生产环境强烈建议这么做 docker pull nginx:1.25-alpinelatest标签是很多线上事故的根源。你今天拉下来的latest和三个月后拉下来的latest大概率不是同一个镜像里面的软件版本、运行时行为都可能有变化。我自己有个原则任何要长期跑的容器镜像tag必须写精确宁可用nginx:1.25.5-alpine这种一长串也不写nginx:latest。镜像拉下来后用docker images或等价的docker image ls查看。这里有个容易看走眼的指标SIZE。它指的是镜像压缩后的大小吗不是是镜像在本地展开后的总和顶层可写层不在其中。你可能会看到同一个nginx镜像在不同机器上SIZE不一样这很正常因为底层基础镜像的基础不同。清理镜像是另一个高频需求。删单条镜像用docker rmi 镜像ID但如果你有大量悬空镜像none标签一条条删太痛苦直接# 清理所有悬空镜像未被任何容器引用的dangling镜像 docker image prune # 清理所有未被容器使用的镜像慎用加了-a会把你本地所有没有容器在用的镜像全删掉 docker image prune -aprune系列命令加上-a后威力很大我建议第一次执行时先不加-a看一眼它列出的待清理清单确认没有需要的再决定要不要扩大范围。Docker的删除命令一旦确认是没有回收站的。2.2 容器生命周期run、ps背后的参数逻辑docker run是Docker命令里参数最多的一个也是新手最先接触的。它的完整形态是docker run [参数] 镜像 [容器内启动命令]比如从镜像启动一个后台运行的Nginx容器docker run -d --name web-demo -p 8080:80 -v /home/user/html:/usr/share/nginx/html:ro nginx:1.25-alpine这一条命令里有五个关键参数建议逐个理解而不是复制粘贴参数作用使用场景-d后台运行detach默认不加的话容器在前台运行CtrlC会直接停掉容器--name给容器起名没名字的话只能用随机ID操作太痛苦-p 8080:80宿主机8080端口映射到容器80端口端口映射是实现“外网访问容器内服务”的核心-v 宿主机目录:容器目录挂载数据卷或目录让容器内文件与宿主机同步详见第五章:ro只读挂载防止容器内误修改宿主机文件安全习惯还有个很容易犯的错误docker run和docker create的区别。run等于create加start一条命令搞定。但如果你只想创建容器不想立刻启动比如要先准备好存储卷再启动可以用docker create这也是脚本编排时常见的操作。容器跑起来之后docker ps是你要看无数次的命令。不加参数只显示正在运行的容器加-a显示所有容器包括已经退出的。补充几个实用变体# 只显示容器ID常用于脚本遍历 docker ps -q # 按状态过滤 docker ps -f statusexited # 自定义输出格式只取名字和端口映射 docker ps --format table {{.Names}}\t{{.Ports}}容器启停有几个层级很多人在这一块搞混docker stop 容器发送SIGTERM信号给足优雅退出的时间超时后SIGKILLdocker start 容器启动一个已存在的容器docker restart 容器等于先stop再startdocker restart --time10 容器可以指定等待时间删除容器的命令是docker rm但如果容器还在运行直接docker rm会报错需要加-f强制删除。-f的本质是先强制stop再删除容器内的进程不会有优雅退出机会所以生产环境慎用rm -f。另外删容器时如果想连它挂载的匿名卷一起删可以加--volumes参数但命名卷不受影响。2.3 进入容器、看日志、监控资源容器是“一个跑着主进程的沙箱”最常用的排查操作就是进去看、看日志、看资源。进入运行中的容器标准姿势是docker exec -it 容器名 bash。如果容器里没有bash试试sh再不行就用那个镜像自带的最小shell。Alpine基础镜像默认只有shDebian/Ubuntu基础镜像一般有bash。注意exec是在容器里新起一个进程用完exit退出就行不影响容器本身。attach和exec的区别经常有人问。attach是把自己挂到容器的主进程上去看到的是主进程的输出退出attach时按CtrlP CtrlQ如果操作不当会把容器一起停掉。exec则是新开一个独立进程退出对容器毫无影响。所以从现在起进入容器一律用exec别碰attach除非你有特殊理由。看日志是排查容器异常的头号手段# 实时滚动看日志 docker logs -f 容器名 # 看最后200行 docker logs --tail 200 容器名 # 带时间戳 docker logs -t 容器名资源监控方面docker stats直接看所有运行中容器的CPU、内存、网络IO。docker top 容器名看容器内的进程列表有时候容器内应用启动失败docker ps里容器还是Up状态但docker top一查发现主进程早就僵死了这种“僵尸容器”在排查时最容易迷惑人。docker inspect是最终的兜底命令它输出容器的完整元数据JSON。想知道容器的启动命令、环境变量、端口映射、挂载点、网络模式、重启次数一条docker inspect 容器名全都有。对着一大坨JSON找字段确实费劲可以配合jq或直接加--format# 查看容器的重启策略 docker inspect --format {{.HostConfig.RestartPolicy.Name}} 容器名 # 查看全部环境变量 docker inspect --format {{range .Config.Env}}{{println .}}{{end}} 容器名2.4 一条命令链路串起Nginx把上面的命令串成一个最小可复现的实操链路看完就能跟着敲一遍# 1. 拉取nginx镜像 docker pull nginx:1.25-alpine # 2. 启动容器宿主机8080映射到容器80 docker run -d --name web-demo -p 8080:80 nginx:1.25-alpine # 3. 确认容器状态 docker ps # 4. 看nginx是否正常输出访问日志 docker logs -f web-demo # 5. 进入容器内部看看目录 docker exec -it web-demo sh ls /usr/share/nginx/html # 6. 重启容器验证配置动态生效 docker restart web-demo # 7. 清理 docker stop web-demo docker rm web-demo这一段链路跑完你对镜像、容器、日志、exec、启停、删除这套基础操作就有完整使用了。剩下的参数都是围绕真实业务场景叠加的——端口冲突了、数据要保存、多个容器要协作这些才是后面几章要解决的问题。3. Docker Desktop安装失败与API连接报错的排查3.1 CLI与Engine是两码事很多人搜“docker命令”时搜到的其实是“docker怎么安装”“docker报错怎么办”。安装阶段的报错和命令使用是强相关的因为Docker本身是C/S架构你敲的每一条docker命令都是客户端CLI它要去连一个叫Engine的守护进程。在Linux上这个进程是dockerd在Windows/macOS上则是Docker Desktop内置的一个Linux虚拟机或WSL2发行版里的引擎。docker version命令能一眼看出两端状态docker version正常输出的结尾会分两段Client和Server。如果只看到Client或者Server那一栏写的是ERROR说明客户端没问题但引擎连不上。这条命令本身也是排查Docker问题的第一句话无论遇到什么奇怪现象先敲它比先重装强得多。3.2 “virtualization support not detected”怎么办Windows上装Docker Desktop最经典的报错之一就是virtualization support not detected或者完整一点是Docker Desktop failed to start because virtualization support was not detected。很多人看到这个就慌了以为Docker安装包有问题。其实这99%是宿主机层面的虚拟化没开跟Docker本身没半毛钱关系。Docker Desktop在Windows上有两种后端WSL2后端和Hyper-V后端。无论哪种都需要CPU虚拟化指令Intel VT-x/AMD-V被启用。排查路径按这个顺序走打开任务管理器 → “性能”标签页 → 看右下角“虚拟化”是不是显示“已启用”。如果显示“已禁用”去BIOS里把Intel Virtualization Technology或AMD SVM Mode打开保存重启。如果虚拟化显示“已启用”但仍然报错多半是WSL2或虚拟机平台没装全。用命令检查# 查看WSL版本和发行版状态 wsl --status # 更新WSL核心 wsl --update # 查看已安装的发行版 wsl -l -v确认Windows功能里开启了“适用于Linux的Windows子系统”和“虚拟机平台”。这两个功能可以在“控制面板 → 程序 → 启用或关闭Windows功能”里勾选也可以用管理员权限PowerShell执行dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart如果WSL2内核异常执行wsl --update之后重启Docker Desktop大多数情况下就能解决。讲个我之前遇到的案例有台电脑任务管理器里显示“虚拟化已启用”但Docker Desktop装完照样报virtualization support not detected。折腾一圈后发现是BIOS里的VT-x确实开了但系统把“基于虚拟化的安全”功能给绕过了另一种说法叫“内核隔离”或“Memory Integrity”。在Windows安全中心里关掉内核隔离并重启后问题就消失了。这种“系统层面吞掉虚拟化能力”的情况排查时很容易漏。3.3 npipe报错与引擎启动验证Windows上另一个让人头大的报错是failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinuxEngine。这个npipe就是Docker Desktop的Linux引擎暴露给Windows客户端的命名管道报错的意思很直白你的docker命令找到了这个管道地址但管道那头没有Engine在监听。这个报错最常规的原因是Docker Desktop还处于启动中或者启动到一半挂掉了。排查命令# 1. 先确认客户端和服务器状态 docker version # 2. 看当前docker context docker context lsDocker Desktop正常工作时会有个context叫desktop-linux并且带*标记表示当前使用中。如果你的默认context指向的是别的地址比如某个远程机器那Windows上敲docker当然会连不上本地Desktop的管道。可以用命令切换docker context use desktop-linux引擎确实没起来时优先检查Docker Desktop的界面状态。它启动时会经历“Starting the Docker Engine”阶段如果停在这个阶段超过一两分钟去查它的日志Docker Desktop主界面菜单 → Troubleshoot → Get support → 打开日志目录。Windows下日志一般在%LOCALAPPDATA%\Docker\log\里重点看error关键字。还有一个常见坑宿主机端口被占用。Docker Desktop启动时会在宿主机的几个固定端口上起服务比如某个版本的Desktop要占用8080或443端口被其他程序占住后引擎会起不来表现同样是npipe报错。用netstat -ano | findstr 端口号查到PID后去任务管理器结束进程再重启Docker Desktop就好。引擎起来之后用docker info看完整的系统信息确认Storage Driver、镜像数量、容器数量都正常。最后跑一个经典验证docker run hello-world这个容器会打出一段欢迎文案说明CLI连接Engine、Engine拉取镜像、创建容器、运行容器的完整链路全部正常。以后任何环境换新机器我都建议先跑这一句再干别的省得后面排查了半天才发现是环境本身有问题。4. 网络不通的排查链路从端口映射到容器间通信4.1 三种网络模式先分清“docker网络不通”是搜索引擎里永不缺席的疑问几乎每个接触Docker的人都会遇到至少一次。排查之前你先得弄清楚你的容器在用哪种网络模式。# 查看所有网络 docker network ls # 查看默认bridge网络的细节 docker network inspect bridgeDocker内置三种默认网络bridge默认模式。容器在一个私有网段里通常172.17.0.0/16通过端口映射对外暴露服务。host容器直接共享宿主机的网络栈不隔离端口性能好但隔离性差。-p参数在host模式下是无效的。none容器没有网络配置主要用于一些纯计算或需要完全隔离的场景。还有一个特殊场景是container:其他容器名让容器共享另一个容器的网络栈很少用但某些调试场景下能救命。看端口映射有两组命令# 列出容器的端口映射 docker port 容器名 # 或者从inspect里精准取 docker inspect --format {{json .NetworkSettings.Ports}} 容器名如果你启动时写了-p 8080:80但docker port 容器名输出为空那这个容器要么没在运行要么启动时端口参数没生效。这是最基础的一层排查。4.2 五步排查链路我总结了一套“容器网络不通”的排查顺序照着走大部分问题能在三步内定位第一步确认容器真的在跑 docker ps 第二步确认映射关系 docker port 容器名 第三步容器内部自测 docker exec -it 容器名 curl -v 127.0.0.1:容器内端口 第四步从宿主机测映射端口 curl -v 127.0.0.1:宿主机端口 第五步如果是容器间通信检查是否在同一网络 docker inspect 容器A --format {{.NetworkSettings.Networks}}这套链路里有几个很隐蔽的坑。第三步里容器内自测的是127.0.0.1但很多应用配置了只监听127.0.0.1而非0.0.0.0。如果应用只听本地回环容器内curl 127.0.0.1能通但宿主机通过映射端口访问时流量到达容器后由于应用不对外监听照样不通。比如你在容器里跑了一个SpringBoot应用server.address配置成127.0.0.1那-p 8080:8080映射了也白搭得改成0.0.0.0或者0.0.0.0模板。这个问题不熟悉网络栈的人很容易忽略映射能生效应用监听地址不匹配表现就是“端口看起来开了但连接被拒绝”。第五步的容器间通信是最多人踩坑的地方。默认bridge网络里的容器之间可以互通但是要用IP地址不能用容器名。因为内置DNS解析只在用户自定义网络里提供。这个我放到下一节专门讲。4.3 自定义网络与容器名解析先看一个最常见的场景一个后端服务要连接一个Redis容器命令大概是docker run -d --name redis-server redis:7-alpine docker run -d --name myapp your-image然后你进myapp里试redis-cli -h redis-server -p 6379 ping大概率是解析不了redis-server这个名字。原因就是两个容器都在默认bridge网络里而默认网络没有内置DNS服务。解决办法是建一个自定义网络把容器都放进去# 1. 创建自定义网络 docker network create my-net # 2. 第一个容器连入 docker run -d --name redis-server --network my-net redis:7-alpine # 3. 第二个容器连入 docker run -d --name myapp --network my-net your-image在自定义网络里Docker自带DNS解析容器名就是主机名。现在redis-server:6379这种写法就能通了。开发环境里几乎所有的多容器协作都应该这么做而不是用IP硬编。因为容器重建一次IP就会变但容器名不变靠名字通信才稳定。如果容器已经跑起来了不想重建可以用动态连接网络docker network connect my-net redis-server docker network connect my-net myapp这个命令在执行时给现有容器增加一个网络接口两条命令执行完一样能互相解析。inspect里能看到容器出现在多个网络的记录。自定义网络还有一个好处是隔离。默认bridge网络里所有容器互相都能访问等于局域网全裸奔自定义网络只允许显式加入的容器互访符合最小权限原则。4.4 DNS和端口冲突的补充还有两类“网络不通”需要单独拎出来说。第一类是容器内DNS解析异常。比如容器里curl一个外网域名解析不了但curlIP地址是通的。这种情况多半是宿主机或容器内的DNS配置有问题。启动容器时可以显式指定DNSdocker run -d --dns 223.5.5.5 --name web-demo nginx:1.25-alpine或者直接在Docker daemon配置里改默认DNS。企业内部有自建DNS的话这个参数几乎必配不然容器一出厂就“断网”。第二类是端口映射失败。典型报错docker: Error response from daemon: driver failed programming external connectivity on endpoint web-demo这句话翻译过来就是端口绑定失败。最常见原因是宿主机那个端口已经被占了。少见一点的是Docker的iptables规则被其他程序改过。排查命令很常规# Linux lsof -i:8080 # 或 ss -lntp | grep 8080 # Windows netstat -ano | findstr 8080 # 查到PID后在任务管理器里结束进程或换个端口重试我自己遇到过一种很恶心的情况Docker服务起来时iptables规则没问题但后来装某个软件时把DOCKER链清掉了导致所有端口映射都失效。这种不会报错但容器通通不通。排查方式是用docker run -d -p 8080:80 nginx试跑个临时容器如果宿主机curl不通再检查系统的iptables转发。复杂环境里这个“试跑最简单容器验证基础网络”的方法确实能帮你把问题边界快速划出来。5. 数据卷与持久化5.1 为什么容器一删数据就没了Docker容器的文件系统由镜像层只读加一个可写层组成。容器一删可写层直接销毁里面写的所有文件跟着灰飞烟灭。这不是Docker的问题而是设计如此容器是“无状态”的随便杀掉随便重建是它的优点。但业务总要存数据。MySQL的库、Redis的持久化文件、应用上传的附件这些如果跟着容器一起删任何团队都受不了。解决方案就是数据卷Volume把数据存到独立于容器生命周期的宿主机或卷存储上。5.2 卷管理命令与挂载语法# 列出所有卷 docker volume ls # 创建一个命名卷 docker volume create mydata # 查看卷详情 docker volume inspect mydata # 删除卷 docker volume rm mydata # 清理所有未被使用的卷 docker volume prune卷本身是个“存储桶”它不绑定任何容器。一个卷可以被多个容器同时挂载也可以被删除后重新创建。挂载方式分三类匿名卷-v /容器路径Docker自动生成一个随机名卷容器删了卷还在但因为没有名字很难定位不推荐。命名卷-v 卷名:/容器路径手动管理推荐。绑定挂载-v /宿主机绝对路径:/容器路径直接把宿主机目录共享给容器使用适合开发时改代码即时生效。命名卷宿主机上的实际存储位置是/var/lib/docker/volumes/卷名/_data但生产环境我不建议直接去那个目录改文件绕过Docker直接操作卷数据容易出权限和一致性问题。绑定挂载没有这层限制宿主机路径在哪就在哪看的清清楚楚。这里补充一个语法细节-v和--mount的写法差异。-v是短参数写法-v 源:目标:选项如果路径里带冒号容易踩坑--mount是长参数写法--mount typebind,source/host,target/container,readonly用逗号分隔多个键值对。新项目里我倾向用--mount可读性更强但-v因为短交互式命令里用得更多。5.3 用MySQL演示持久化拿MySQL做例子最直观。先启动一个带命名卷的MySQLdocker run -d \ --name mysql8 \ -e MYSQL_ROOT_PASSWORDmy-secret-pw \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ mysql:8.0连接进去建个库建张表docker exec -it mysql8 mysql -uroot -p然后执行CREATE DATABASE testdb; USE testdb; CREATE TABLE students (id INT PRIMARY KEY, name VARCHAR(50)); INSERT INTO students VALUES (1, zhangsan);退出后模拟最极端的情况——删掉容器docker rm -f mysql8用同样的命令重新拉一个容器挂载同一个卷docker run -d \ --name mysql8-new \ -e MYSQL_ROOT_PASSWORDmy-secret-pw \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ mysql:8.0进入新容器查数据docker exec -it mysql8-new mysql -uroot -p SELECT * FROM testdb.students;你会发现数据全在。这就是持久化的全部要点容器是自来水管道卷是蓄水池管道换一根水池水位不变。用docker inspect 容器名的Mounts字段可以看到挂载详情docker inspect mysql8-new --format {{json .Mounts}}5.4 备份与迁移卷数据怎么备份既不用进容器去docker cp也不用从/var/lib/docker/volumes底下拷。一个标准的方案是用一个临时容器挂载卷后打包# 把mysql-data卷打包到当前目录 docker run --rm \ -v mysql-data:/data \ -v $(pwd):/backup \ alpine tar czf /backup/mysql-data.tar.gz -C /data .备份文件mysql-data.tar.gz就会出现在宿主机当前目录。恢复时反向解包# 先创建一个空卷 docker volume create mysql-data-restore # 把备份解压进卷 docker run --rm \ -v mysql-data-restore:/data \ -v $(pwd):/backup \ alpine tar xzf /backup/mysql-data.tar.gz -C /data这个思路不仅适用于MySQL凡是挂载了命名卷的应用都可以用这套“临时容器 tar”的流程备份迁移逻辑简单、不依赖额外工具。绑定挂载的目录因为本来就是宿主机文件直接tar打包目录即可不需要走Docker。6. Docker Compose与青龙面板实战6.1 新旧命令差异docker-compose 与 docker compose到了多容器阶段一条条docker run就没法看了。服务一多启动顺序、环境变量、网络共享、重启策略都是配置化的需求这就要上Compose。先区分一个常见困惑docker-compose带横线是老版本独立的Python二进制docker compose空格分隔是Docker CLI内置的插件子命令。新版Docker默认都有docker compose我用的一直是后者。# 查看compose子命令 docker compose --help6.2 compose命令族的日常用法Compose文件是yaml格式默认叫docker-compose.yml或compose.yml。所有操作都基于这个文件来# 后台启动所有服务 docker compose up -d # 启动并强制重新构建镜像 docker compose up -d --build # 查看服务状态 docker compose ps # 实时看所有服务日志-f相当于tail docker compose logs -f # 只看某个服务的日志 docker compose logs -f 服务名 # 进入某个服务容器 docker compose exec 服务名 bash # 停止并删除所有容器数据卷默认保留 docker compose down # 连数据卷一起删危险操作慎用 docker compose down -v # 校验compose文件格式快速发现缩进和字段错误 docker compose config -q有一个细节值得注意修改了compose文件里的配置后很多人直接docker compose restart但restart只是重启容器不会重新读取配置。正确姿势是docker compose up -d。它会比对配置和当前容器状态发现配置变了就重建受影响的服务。这个对比动作靠的是一个hash所以哪怕只改了环境变量容器也会重建。6.3 青龙面板依赖管理的compose青龙面板qinglong是一个很多人在用的定时任务管理面板部署门槛主要在“依赖管理”。容器本身能正常启动但面板里的脚本一跑就报模块找不到、命令找不到这是出现频率最高的现象。看一个典型的compose文件services: ql: image: whyour/qinglong:latest container_name: ql ports: - 5700:5700 volumes: - ./ql/data:/ql/data environment: - TZAsia/Shanghai restart: unless-stopped启动docker compose up -d打开http://宿主机IP:5700就能看到面板。但面板能访问只是第一步定时任务脚本能不能跑看的是容器内有没有对应的运行时依赖。青龙面板自带了一部分Node、Python、Go环境但脚本要用的第三方包比如axios、requests、各种npm/pip包不一定都装好了。依赖管理的两种主流操作方式一种临时调试一种长期稳定# 方式一直接进入容器安装当时就能生效 docker compose exec ql bash # 进入后在容器内用包管理命令装 pnpm add axios pip install requests这种方式装完就能用但容器一重建这些依赖就没了。适合临时调试某个脚本。# 方式二在面板的“依赖管理”里配置要安装的依赖面板会按声明去装青龙面板有依赖管理的页面你在里面填上需要预装的依赖名称面板自己会调用容器内的包管理工具去装。这种方式对“容器重建后依赖还在不在”这个问题处理得更好因为依赖清单是持久化在/ql/data里的数据卷没丢清单就在。实际踩坑经验挂在./ql/data的目录不能乱换权限面板在容器内对数据目录有严格的属主和权限要求。如果宿主机上chmod或chown改错了面板可能随机报“文件不可写”“模块找不到”等诡异错误。我的习惯是第一次挂载之前先看官方文档确认基础目录要求挂载后除非必要否则不动权限。另外一个高频坑是时区没配TZAsia/Shanghai如果漏了任务执行时间会比北京时间偏差刚部署时极难察觉等发现时任务已经跑歪了。6.4 多服务编排与排错青龙面板是单服务的案例实际业务里常见的是“应用 缓存 数据库”的三角组合。看一个精简示例services: app: build: . ports: - 8080:8080 environment: - REDIS_HOSTredis - DB_HOSTmysql depends_on: redis: condition: service_healthy mysql: condition: service_healthy restart: unless-stopped redis: image: redis:7-alpine volumes: - redis-data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 5s retries: 5 mysql: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORDsecret volumes: - mysql-data:/var/lib/mysql healthcheck: test: [CMD, mysqladmin, ping, -h, 127.0.0.1] interval: 5s retries: 5 volumes: redis-data: mysql-data:这里有两个知识点。depends_on控制启动顺序但如果只是简单声明Docker只保证“先启动顺序”不保证依赖服务“已经可用”。MySQL容器启动完成不代表MySQL进程就绪了Redis也一样。所以要配合healthcheck用condition: service_healthy让app等依赖真正健康后才会启动。这也解释了为什么很多人的app容器一启动就连不上Redis和MySQL——时序没控制好。Compose排错三件套是我的固定套路# 1. 检查配置文件是否合法 docker compose config -q # 2. 看服务状态哪绿的哪红的 docker compose ps # 3. 看具体服务日志 docker compose logs -f app顺序别反。先config -qyaml缩进错误和字段拼写错误一秒就能暴露不用白跑其他命令再ps看哪些服务起不来最后才logs深入看原因。多数Compose问题这三步走完就定位了。7. Dockerfile构建命令与镜像瘦身7.1 build命令与上下文的概念Dockerfile写好后用docker build构建镜像。先讲一个很多人理解错误的点docker build -t myapp:1.0 .最后那个.是什么意思它不是把当前目录简单全包进去而是指定“构建上下文”。Docker会把整个上下文目录打包发送给daemonDockerfile里的COPY只能访问上下文范围内的文件。如果构建上下文里有个500MB的node_modules目录这500MB也要被完整发送构建速度瞬间拉垮。所以每个项目必须配.dockerignore文件语法和.gitignore一致node_modules .git *.log我在给前端项目做Docker化时深有体会没配.dockerignore之前一次构建要三四分钟配完之后几十秒就完成。构建上下文的体积直接决定构建耗时这是初学者最容易忽视的性能瓶颈。其他常用构建参数# 构建时不使用缓存老老实实每一层都重跑 docker build --no-cache -t myapp:1.0 . # 指定Dockerfile路径 docker build -f ./deploy/Dockerfile -t myapp:1.0 . # 构建时传递环境变量 docker build --build-arg VERSION1.0 -t myapp:1.0 .7.2 层缓存机制为什么构建快慢差别那么大Dockerfile里的每条指令都会生成一个镜像层。层的另一个特性是缓存某一层如果满足缓存条件构建时会被直接复用后面的层才会重新生成。这个机制帮你省了大量时间但也坑了很多人。先看一个反面教材FROM node:18-alpine WORKDIR /app COPY . . RUN npm install EXPOSE 3000 CMD [npm, start]这条Dockerfile最大的问题是COPY . .在前RUN npm install在后。只要项目里任何一个文件变了COPY . .这一层的hash就变了后面的RUN npm install的缓存完全失效。哪怕这次只是改了一行READMEnpm install也要从头跑一遍几百个包重新下载。正确的姿势是让“最不容易变的指令”靠前“最容易变的指令”靠后FROM node:18-alpine WORKDIR /app # 先把依赖清单拷进去 COPY package.json package-lock.json ./ # 单独装依赖package.json不变这一层就缓存 RUN npm install # 最后才拷源代码 COPY . . EXPOSE 3000 CMD [npm, start]这样只要package.json没变不管源代码改了多少次npm install都会直接命中缓存构建时间锐减。类似的逻辑适用于Python的requirements.txt、Go的go.mod、Java的pom.xml。构建缓存是Docker提速的最大杠杆而这个杠杆的支点就是Dockerfile指令排序。用docker history 镜像名可以看每层的创建记录用来验证各层是否正常生成。排查构建缓存问题时这个命令很有用。7.3 多阶段构建让镜像从1GB瘦到几十MB给编译型语言做镜像时最大的体积问题是既要用完整编译工具链才能build出来运行的时候却根本不需要那些工具。多阶段构建的思路是分两个阶段第一个阶段用完整开发镜像编译第二个阶段只把编译产物拷贝进一个精简运行镜像。以Go项目为例# 第一阶段编译 FROM golang:1.22-alpine 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 WORKDIR /app COPY --frombuilder /app/myapp . EXPOSE 8080 CMD [./myapp]构建出来的镜像里只有一份静态二进制文件和系统基础文件没有Go编译器、没有源码、没有下载的模块缓存。我看过不少人给Go项目直接在golang:1.22上跑镜像体积轻松超过1GB换成多阶段构建后因为用了alpine基础镜像体积直接降到20MB左右。Java和前端项目也有类似套路比如前端构建完只把dist目录拷进Nginx镜像。这个优化不是可选项是必然选项。镜像体积直接影响拉取速度、磁盘占用和启动时间生产环境里大镜像的危害还会传导到发布效率上。除了多阶段构建几个惯性操作也能明显瘦身基础镜像优先选-alpine、-slim这类精简变体而不是默认的full版。一个RUN里做完“安装→使用→清理缓存”避免中间产物残留在层里RUN apt-get update \ apt-get install -y --no-install-recommends curl \ rm -rf /var/lib/apt/lists/*不要把源码和密钥COPY进镜像。这是安全和体积双重问题。7.4 镜像的推送、保存与传输镜像构建完总要推送到仓库或者分发给其他机器。最常用的是推送到镜像仓库# 先给镜像打上仓库地址的tag docker tag myapp:1.0 registry.example.com/myapp:1.0 # 推送到远程仓库 docker push registry.example.com/myapp:1.0 # 从远程仓库拉取 docker pull registry.example.com/myapp:1.0单机离线环境下用save和load打包传输# 将镜像保存为tar包 docker save -o myapp.tar myapp:1.0 # 在其他机器加载 docker load -i myapp.tar注意save保存的是完整镜像包括所有层体积比较大但传输和应用都可靠。export是另一个命令它导出的是容器的文件系统不包含镜像历史信息所以一般不用它做镜像分发。最后说一个很多人问过的问题能不能用docker commit把改了文件的容器做成新镜像比如在容器里手动装了一堆依赖后docker commit。能用但我强烈不建议。commit产生的镜像没有构建历史你根本不知道里面装了什么、每层做了什么无法审计也无法复现。正确的做法是把操作写成Dockerfile保证任何时刻都能从头重建。容器里做的临时修改只适合调试不适合沉淀成镜像。构建缓存被破坏还能重新跑一遍构建历史和可复现性丢了那才是更大的麻烦。最后分享一个我自己的习惯。新环境也好老环境出怪事也好我不会急着写一堆业务命令而是先跑docker version和docker info确认CLI和Engine都在然后docker run hello-world验证最基础的运行链路确认底层没问题后再去碰具体业务容器。排查问题时尽量顺着“容器起来没有 → 端口映射对不对 → 容器内部自测 → 跨容器通信 → 数据卷状态”这个顺序走。按这个套路走九成的问题不会白折腾。Docker命令说白了就是一套查状态的工具状态查清楚了命令自然就用对了。
返回列表