资讯动态

用蜜雪冰城SOP思维彻底搞懂Dockerfile:从标准作业到容器镜像构建

发布时间:2026/9/16 3:45:29 来源:尧图企业网站定制
如果你在奶茶店打过工或者只是排队买奶茶时多看了几眼操作台你会发现几乎所有连锁奶茶品牌都有一个共同点柜台后面贴着一张塑封的纸。上面写着某款饮品的标准做法——茶叶多少克、水温几度、泡几分钟、加多少糖浆、冰块几勺、摇晃几下、什么时候封口。这张纸行话叫SOP标准作业程序。我第一次在技术群里看到有人用蜜雪冰城来比喻 Dockerfile第一反应是有点好笑但仔细一想又觉得极度贴切。Dockerfile 本质上就是一张写给 Docker 看的“饮品制作说明书”基础镜像选什么、要安装哪些依赖、拷贝哪些文件、执行哪些命令、容器启动时跑什么程序每一步都写得清清楚楚。任何人、任何机器照着执行理论上都能得到一模一样的镜像。这篇文章就把“用蜜雪冰城 SOP 的思维理解 Dockerfile”这件事彻底讲透。不管你是第一次接触容器化、刚准备写 Dockerfile 的新手还是已经在线上跑了不少镜像、想系统补一补底层概念的同学这篇文章都很适合你。我会从核心指令讲起给出可以直接抄作业的完整案例再把那些 SOP 里没有写、但迟早会踩到的坑一个个排出来。1. 蜜雪冰城 SOP 和 Dockerfile 到底哪里像1.1 先搞清楚蜜雪冰城 SOP 的本质蜜雪冰城的 SOP 之所以值得拿来当比喻不是因为它有多高级恰恰相反它的核心在于“极度标准化”。一家普通的独立奶茶店做一杯柠檬水可能全凭师傅手感——今天柠檬多切两片明天糖浆多按一下后天冰块加半杯。但连锁品牌不能这么干因为它的每一家门店、每一个员工、每一天的出杯都必须尽量一致。为了保证一致性它只能把制作流程拆成非常具体、可执行的步骤。这就是 SOP 的本质不是告诉店员“这杯要做得酸甜可口”而是告诉他“柠檬 2 片、果蜜 30ml、冰块 180g、饮用水至 500ml 刻度线”。拆得越细执行越死板结果越可控。做得好不好喝是配方设计的问题但至少每家店的味道不会跑偏太多。这一点放在 Dockerfile 上完全成立。Dockerfile 里的每一行都是对容器运行时环境的精确规定。它不讲“这个服务要跑在干净的环境里”而是直接写FROM ubuntu:22.04、RUN apt-get install -y nginx、COPY ./dist /usr/share/nginx/html。这些指令拆得越精确构建出来的镜像就越可预期。1.2 Dockerfile给机器看的“饮品制作说明书”我们平时看到的 Docker 镜像本质上是一个“打包好的运行环境”。这个环境里可能装了一个 Ubuntu 系统可能装了 Python 解释器可能放了一份代码也可能预先配好了 Nginx 和证书。但问题是这个环境是怎么一步一步组装起来的如果某台构建机器上曾经被手工安装过乱七八糟的东西那它构建出来的镜像跟一台全新机器构建出来的镜像几乎是必然不一样的。Dockerfile 存在的意义就是把这套“手工组装环境”的过程全部变成明文记录。所以 Dockerfile 就是一份给机器执行的 SOPDocker 不会凭空想象“嗯这个镜像应该装个 Node.js 14”它只会老老实实地执行FROM node:14-alpine然后执行后面每条指令。凡是 SOP 里没写的它一概不干凡是 SOP 里写了的少一行都不行。蜜雪冰城 SOPDockerfile原料清单茶底、果蜜、柠檬基础镜像、依赖包、项目文件标准操作泡茶、加糖、加冰、摇匀RUN、COPY、ENV 等指令出品标准容量、温度、封口CMD、ENTRYPOINT 定义的启动行为店员照着做每家店味道一致任何机器照着构建镜像一致1.3 这个比喻为什么成立从一杯奶茶看镜像本质再往深一层想。蜜雪冰城的 SOP 跟一杯已经做好的成品奶茶之间是什么关系SOP 负责描述制作方法但它本身不是奶茶。奶茶门店按照 SOP 制作产出的才是可以卖给顾客的实物。镜像也是同理Dockerfile 负责描述构建方法但它本身不是镜像。Docker 引擎按照 Dockerfile 里的指令一步步执行产出的那个不可修改的、静态的压缩包才是镜像。换句话说Dockerfile 是“怎么做”镜像是“做出来的东西”容器则是“把这杯奶茶放在你面前插上吸管可以喝的状态”。这里有个特别容易混淆的点我见过很多同学在做镜像和写 Dockerfile 的时候会绕弯子他们会先启动一个容器然后在容器里手工安装软件、手工改配置、手工跑命令最后再docker commit把容器提交成镜像。这相当于什么呢相当于店员奶茶店里随手抓了一把茶叶、一勺糖、几块冰凭感觉做了一杯“差不多”的成品然后把这个成品当成标准样品送去店里复刻。问题是这个过程没有留下任何记录没有人知道茶叶到底是 5g 还是 8g糖浆是 20ml 还是 40ml。下次构建的时候只能再来一次“灵性操作”。用 Dockerfile就是把这个过程从“玄学”变成“科学”。这个转变意识比会写几条指令重要得多。2. 像写奶茶配方一样拆解 Dockerfile 核心指令既然把 Dockerfile 当说明书那说明书里最常用的几个指令就是配方里的“固定句式”。这一节我把它们逐个拆开讲清楚每条指令是干什么的、为什么要这样写、常见的坑在哪里。2.1 FROM选好基底茶底这件事决定了你到底叫什么名字做奶茶第一步不是加糖不是加冰而是选茶底。选的是红茶、绿茶还是乌龙茶直接决定了这杯饮品的基底路线。选错了茶底后面再怎么调整都是白费。Dockerfile 里对应这一步的就是FROM指令它指定了当前镜像基于哪个基础镜像构建。FROM是绝大多数 Dockerfile 的第一行除非用ARG定义基础镜像变量因为它决定了整个镜像的文件系统起点。例如FROM centos:7 RUN yum install -y vim如果基础镜像自带的是老旧的 glibc 和编译器那后面编译任何东西都可能踩坑如果基础镜像自带的是 miniconda那 Python 虚拟环境就省事很多。我见过不少项目的 Dockerfile 第一行永远都是FROM centos:7理由是“以前这么写习惯了”。但 CentOS 7 停止维护之后yum 源失效、安全补丁不更新用这种镜像上生产等于抱着一个定时炸弹。选基础镜像可以简单参考三条原则优先选官方镜像名字里带-alpine、-slim等精简版的优先明确锁定版本号不要用latestJava 项目优先选自带 JRE 的镜像Node.js 项目优先选自带 LTS 版本的镜像不要自己去装运行时。latest这个标签在 Docker Hub 上表示“最近一次推送的最新版本”但它不是固定不变的。今天构建的镜像用的是 Node 18三个月后可能已经变成 Node 20期间你的代码可能没做任何修改但行为已经完全变了。这种锅我背过排查到最后发现线上镜像用的 Node 版本和本地开发差了三个大版本。2.2 RUN每一条命令都是一次“加料、搅拌、熬煮”RUN是出现频率最高的指令它指定了构建过程中要执行的命令。这些命令会在镜像的“临时容器”里运行运行结果会被提交到镜像层中。比如要给镜像装一个 curl可以这样写FROM ubuntu:22.04 RUN apt-get update apt-get install -y curl RUN rm -rf /var/lib/apt/lists/*写RUN有两点要特别注意。第一多用合并命令少写多个 RUN 串行执行。因为 Dockerfile 中每一条 RUN 都会产生一个新的镜像层镜像层越多体积越大、传输越慢。上面例子里的apt-get update apt-get install合并成一句话还有一个重要原因如果拆开写apt-get update 可能在前一层就把缓存写进镜像而下一层再安装包时apt 的缓存文件路径已经固定反复构建容易踩缓存问题。第二包管理器安装后要及时清理缓存。apt-get install之后apt 会留下大量索引文件和下载缓存这些对运行时完全没用却会白白占用镜像体积。所以在同一个 RUN 里安装完马上清理这跟“食材用完要密封好放回冰箱”是一个道理。2.3 COPY 与 ADD备料入库和加小料的区别做饮品需要往杯子里加柠檬片、加珍珠、加奶盖。这些原料不是自动出现的必须有人提前准备好再按步骤放进杯里。镜像里对应这个动作的是COPY和ADD指令。COPY的作用很简单把构建上下文里的文件或目录原封不动地复制到镜像里。例如COPY package.json /app/package.json COPY dist/ /usr/share/nginx/html/ADD看起来跟 COPY 很像但它多了一些额外功能能自动解压本地 tar 包还能从远程 URL 下载文件。听起来很方便但实际工作中我建议优先使用COPY原因有三点ADD 的“自动解压”行为可能带来意外比如你本来只想把压缩包塞进镜像备用结果它被自动解压了ADD 从远程 URL 下载文件会把网络请求延迟带进构建过程而且下载的文件来源不受 Docker 缓存细粒度控制ADD 的语义比 COPY 模糊后续维护的人不一定清楚这个文件是“原样拷入”还是“已经解压过”。有一种场景 ADD 确实好用就是把一个本地 tar 包解压进镜像ADD jdk-17_linux-x64_bin.tar.gz /opt/jdk/这条命令会自动把 jdk 压缩包解开并放到 /opt/jdk/ 下。这个行为很省事但如果你并不确定要不要解压那还是老老实实用 COPY 一次性把文件拷进去后续需要时再用 RUN 解压逻辑更透明。2.4 ENV、ARG、WORKDIR把“甜度冰量”参数化蜜雪冰城的菜单里柠檬水可以选正常冰、少冰、去冰甜度也可以调。如果 SOP 上只有一种甜度、一种冰量那一部分顾客可能就不满意了。为了兼容不同需求SOP 会定义“标准化参数”门店再按顾客要求微调。Dockerfile 里承担“标准化参数”功能的指令是ENV和ARG。ENV定义的是环境变量它会在构建阶段和容器运行阶段都生效。比如设置 Java 的堆内存参数可以在 Dockerfile 里写ENV JAVA_OPTS-Xms512m -Xmx1024mARG定义的是构建参数只存在于构建阶段不会进入运行中的容器。它可以用来给 Dockerfile 传“客户要求的甜度”ARG APP_VERSION1.0.0 RUN echo building version ${APP_VERSION}构建时可以通过--build-arg APP_VERSION2.0.0覆盖默认值。这在做测试环境和生产环境镜像区分时非常有用。WORKDIR则是“切换工作目录”的指令。它等价于在每条命令前执行cd /app但它比写cd更可靠它会在目录不存在时自动创建目录而且对后续层的RUN、CMD、COPY都生效。注意每条 RUN 命令的执行环境是相互独立的如果你在一条 RUN 里cd /app下一条 RUN 仍然会在默认目录里执行。用 WORKDIR 就能一劳永逸。2.5 CMD 与 ENTRYPOINT出杯标准动作决定了这杯奶茶能不能卖了一杯奶茶做完最后一道工序是封口、装袋、递给顾客。容器镜像构建完成后也要有一个“出杯”动作——容器启动时默认要执行什么程序。这个动作由CMD或ENTRYPOINT定义。CMD提供的是“默认启动命令”它会在启动容器时执行但如果用户在docker run后面输入了额外命令CMD 会被覆盖。例如FROM node:18-alpine CMD [node, server.js]启动时执行docker run myapp容器会跑node server.js但如果执行docker run myapp ls容器会忽略 CMD去跑ls。这就是“默认”的含义。ENTRYPOINT则是“固定的出杯动作”它不会被docker run后面的参数直接覆盖。例如FROM python:3.11-slim ENTRYPOINT [python, app.py]容器启动会始终执行python app.py这种情况下如果还传了启动参数会被当作python app.py后面的命令行参数。配合使用效果更好ENTRYPOINT [python] CMD [app.py]启动时既可以执行python app.py也可以执行docker run myimage other.py来跑脚本灵活性和固定性兼顾。一个常见的坑是使用 shell 写法而不是 exec 写法。CMD python app.py和CMD [python, app.py]看起来差不多但前者会被/bin/sh -c包装一层导致 PID 1 不是 python而是 shell 进程。这会影响容器中信号传递比如docker stop时 SIGTERM 信号可能无法送到 python 进程造成容器无法优雅退出。尽量使用 exec 写法即 JSON 数组格式。3. 手把手写一个“蜜雪冰城版”Dockerfile概念讲了不少这一节直接来个完整案例。我会用最常见的 Node.js Web 服务做示例再额外展示一个 Java 多阶段构建的例子。每一步我都会解释“为什么要这么写”而不是只给一段能跑的代码。3.1 场景设定把 Node.js 服务做成镜像假设现在手上有一个 Express 写的 Web 服务项目目录长这样myapp/ ├── package.json ├── package-lock.json ├── .dockerignore ├── server.js └── src/目标很明确把整个服务打包成一个能独立运行的镜像。镜像里需要 Node.js 运行时、项目依赖代码、启动命令。参考蜜雪冰城的思路我得先把这份“产品配方”拆成几大步找一个干净的“茶底”环境基础镜像把依赖声明文件复制进去安装依赖把业务代码复制进去声明端口、启动命令。3.2 完整 Dockerfile 逐行拆解# 第一行选择茶底。这里用 Node.js 18 的 Alpine 精简版 FROM node:18-alpine # 设置工作目录后续指令都在这个目录里执行 WORKDIR /app # 先把依赖清单复制进去这一步是为了充分利用构建缓存 COPY package.json package-lock.json ./ # 安装生产环境依赖并清理 npm 缓存 RUN npm ci --omitdev \ npm cache clean --force # 再复制全部业务代码 COPY . . # 声明容器对外的端口 EXPOSE 3000 # 以非 root 用户运行这是安全红线 USER node # 设置环境变量 ENV NODE_ENVproduction # 出杯动作启动服务 CMD [node, server.js]逐行解释几个关键点。FROM node:18-alpineAlpine 是一种精简的 Linux 发行版体积小、基础程序少非常适合做运行时镜像。Node 官方提供了基于 Alpine 的版本Java 的则要选 JRE 版本而不是 JDK。COPY package.json package-lock.json ./这里有两个细节。一是为什么先拷贝 package.json 而不是直接COPY . .这跟 Docker 构建缓存机制有关。Docker 构建时每一条指令都会生成一层如果某条指令涉及的文件没有变化Docker 可以直接复用之前构建出来的缓存层。把 package.json 单独拷进来先跑npm ci意味着只要依赖声明文件不变这一层就不会重跑。如果先COPY . .那么源码里任何一个文件改动都会导致整层缓存失效每次都要重新安装所有依赖。二是为什么用npm ci而不是npm installnpm ci会严格按照 package-lock.json 安装版本构建可复现性更好速度也更快。RUN npm ci --omitdev--omitdev表示只装生产依赖测试框架、构建工具这类 devDependencies 不进入镜像能明显缩小镜像体积。USER node这个步骤很多人会漏掉。Docker 容器默认用 root 用户运行但实际生产环境里进程以 root 身份运行非常危险——一旦容器被攻破攻击者拿到的就是 root 权限。Node 官方镜像自带了一个名为 node 的非 root 用户所以这里直接用USER node切换到它。EXPOSE 3000严格来说它只是“声明”端口并不会真正把端口映射出来。真正让端口对外可访问的是docker run里的-p 3000:3000参数。但EXPOSE相当于文档性质的说明告诉使用者这个镜像默认监听哪个端口建议写上。.dockerignore文件也值得放进项目node_modules npm-debug.log .git .gitignore Dockerfile .dockerignore这个文件的作用是告诉 Docker哪些内容不进入构建上下文。尤其是 node_modules 目录如果忘记忽略它会跟业务代码一起被发给 Docker 守护进程拖慢构建速度甚至导致本地的模块覆盖镜像里的版本。构建命令docker build -t myapp:1.0.0 .运行docker run -d -p 3000:3000 --name myapp myapp:1.0.03.3 多阶段构建Java 项目怎么“中央厨房化”做奶茶的中央厨房有两种工作一是前期加工原料二是后期配送。前期加工需要的大锅、蒸炉、刀具后期门店并不需要——门店只需要已经处理好的半成品。Dockerfile 里的多阶段构建就是这个思路先用一个“重装备”镜像完成编译、构建等前期工作再把构建产物复制到一个干净、精简的“运行镜像”里。Java 项目的典型写法# 第一阶段编译 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 第二阶段运行 FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --frombuilder /build/target/myapp.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]这个阶段里最核心的一句话是COPY --frombuilder它表示从第一阶段的产物里把编译好的 jar 包复制到最终镜像。第一阶段里的 Maven 和各种编译依赖不会进入最终镜像所以最终镜像体积会小很多也更安全。这里有一个非常有用的中间步骤RUN mvn dependency:go-offline。它先把 pom.xml 里声明的依赖全部下载到本地 Maven 仓库这样后续代码变更重新构建时只要依赖没有变化这个依赖下载层就可以命中缓存不用把几万个 jar 重复从远程仓库拉一遍。3.4 镜像瘦身如何给镜像“去糖去冰”用完这一套流程之后你会发现镜像体积还是比较大。没关系镜像瘦身就像点奶茶时要求“去糖去冰”能在不影响功能的前提下做得更精简。拿刚才的 Node.js 案例说主要的瘦身手段有以下几条。第一选用更小的基础镜像。node:18-alpine 比 node:18 小不少如果项目没有依赖原生编译模块完全可以放心用。Python 项目可以选 python:3.11-slimJava 项目可以选带 jre-alpine 的镜像。第二清理包管理器缓存。npm 装完依赖后需要清缓存apt 装完包后需要删/var/lib/apt/listsyum 装完包后需要 clean all。这些缓存只在安装时需要运行阶段完全用不到。第三合并 RUN 指令减少镜像层数。每一条 RUN 会新建一层层数越多镜像总大小越大。把相关的命令通过合并起来可以让层数大大减少。第四不要安装用不到的工具。有些项目组会习惯性地在镜像里装 vim、curl、net-tools 方便排查问题但相当于往配方里偷偷加了半斤糖——口感一时爽体积和风险都会上来。排查问题可以用docker exec挂载调试工具或者临时启动一个辅助容器。构建完成后可以用一条命令查看镜像体积docker images如果看到myapp:1.0.0体积只有 150MB 左右而之前的版本是 1.2GB那瘦身的效果就非常直观了。4. 实操中一定会踩的坑SOP 里没写但你迟早会撞上写 Dockerfile 这件事看起来就是照着格式写几条指令、跑一次docker build而已但真正上手之后你会发现坑比想象中多得多。下面这几个问题是我在服务化改造和排障过程中踩过或者帮别人排查过的每一个都很有代表性。4.1 时区问题凌晨三点定时任务永远差 8 小时这个问题非常经典。官方的基础镜像默认时区是 UTC而国内服务通常需要东八区时间。如果你在镜像里跑定时任务、写日志时间戳或者做时间相关的业务计算很快就会发现日志时间和实际时间对不上。解决方式是在 Dockerfile 里直接设置时区FROM ubuntu:22.04 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezoneAlpine 镜像里没有tzdata则需要先安装FROM node:18-alpine RUN apk add --no-cache tzdata \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone最好是拿到一个新镜像第一件事就验证date命令的输出。很多环境变量解决不了的问题都是因为时区没有在构建阶段固化下来。4.2 缓存失效为什么我只是改了一行 READMEnpm install 又跑了一遍前面提过Dockerfile 的构建缓存是按层判定的。如果一条 COPY 指令涉及的文件内容变化这一层和后面的所有层都会缓存失效。这就是为什么你只是改了一行 README整个 npm install 就重新执行了——因为你用的是COPY . .一次性拷贝而不是先拷贝依赖清单再拷贝源码。正确做法我已经在前面写了先COPY package.json package-lock.json ./再RUN npm ci最后COPY . .。这样做之后只要依赖声明文件不变依赖安装层就能命中缓存源码改动只影响后面的 COPY 层和 CMD 层。还有一个隐藏细节COPY指令会判断目录元数据是否变化。如果你用COPY . .即使源码内容没变但文件权限、修改时间变了缓存也可能失效。所以依赖文件锁定良好的项目构建速度一般都比没有锁定的项目快很多。4.3 依赖装不上、下载慢“换源”也要讲究基本法这个问题出现在所有需要从公网拉取依赖的场景里。npm、pip、apt、maven 各自有各自的源网络不稳定的时候都很头疼。在 Dockerfile 里换源是一个常规操作但要注意几个细节。先看 npm 的例子RUN npm config set registry https://registry.npmmirror.com \ npm ci --omitdevpip 的例子RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \ pip install -r requirements.txtmaven 的例子通常是在pom.xml里加 mirror 配置或者在~/.m2/settings.xml里配置镜像仓库。换源能极大提升构建速度但务必注意换源不能依赖临时手写地址要固化在 Dockerfile 或者项目配置文件里否则换一台构建机就又慢了同时有些源并不是实时同步所有包偶尔出现新包拉不到的情况这时候要把默认源和镜像源都配置上做一个 fallback。4.4 容器一启动就挂Entrypoint 和 CMD 的“打架”现场很多新手第一次跑容器执行docker run后发现容器秒退docker logs里看到一堆报错。这里面最典型的原因之一是 CMD 和镜像里已有的启动命令冲突或者 ENTRYPOINT 和 CMD 拼在一起得到了一条不存在的命令。比如你在 Dockerfile 里写ENTRYPOINT [python] CMD [app.py]运行docker run myimage时实际执行的是python app.py。看起来没问题。但如果你习惯了在docker run后面追加参数比如docker run myimage --debug实际执行的是python --debugPython 报错说没有 --debug 这个选项容器就挂了。这不是 Python 的问题而是你把参数当成了要执行的脚本名。排查这类问题有个固定思路看容器启动命令到底拼接成了什么。不用靠猜直接用docker inspect container查看 Config.Cmd 和 Config.Entrypoint 字段或者用docker history image看镜像的元数据。排障效率和靠猜完全不是一个级别。4.5 镜像里全是要命的坑安全红线速查安全意识要刻在 SOP 里。我见过不少团队的 Dockerfile跑在 root 下管理密码直接写在环境变量里安装源还留着调试用的工具包。这些习惯放在内网可能看不出问题一旦镜像被推送到公共仓库或者被攻击者拿到后果会很严重。以下几个红线建议直接写进团队的代码规范不要用 root 运行应用进程尽量用USER指定非特权用户不要把数据库密码、令牌、私钥构建进镜像。环境变量要用运行时注入比如 docker run 的-e参数或容器编排平台的 secret 功能越小越安全不要安装用不到的包和工具基础镜像要跟踪 CVE 漏洞定期用docker scan或 Trivy 之类的工具做安全扫描尽量不要用ADD从远程 URL 拉取文件因为构建过程难以审计且如果源站被篡改镜像安全性直接崩盘。这些项不需要一次全部做到但至少一开始写 Dockerfile 就要有“最小化”和“非 root”这两条底线。5. 把“SOP 思维”从 Dockerfile 延伸到整个容器体系理解了 Dockerfile 的核心其实你也就摸到了容器化体系的半扇门。剩下的半扇门是标签、编排、多平台构建这些既有边界又互相咬合的内容。这一节简单说说怎么把 SOP 思维延伸到容器体系的各个角落。5.1 镜像标签给每杯奶茶打上制作日期和版本镜像标签相当于奶茶杯上的贴纸记录了这杯饮品是什么时候做的、是哪个配方版本。latest标签之所以不建议在生产环境用是因为它指代不明确——到底是“最新稳定版”还是“昨晚凌晨构建的实验版”没人说得清。推荐的做法是给镜像打上语义化版本号再加上构建时间和 commit号docker build -t myapp:1.2.0 . docker tag myapp:1.2.0 myapp:1.2.0-20240615在 CI/CD 流水线里可以用git rev-parse --short HEAD拿到当前 commit 的短哈希拼进镜像标签。这样线上跑了哪个版本、对应哪次提交一眼就能查清楚。5.2 docker-compose多款饮品同时出杯的排班表一份 SOP 只负责一杯饮品门店同时要卖十几款饮品就需要一套多任务的排班表。docker-compose 就是这套排班表它可以一次性拉起多个容器并管理它们之间的网络和依赖关系。services: web: build: . ports: - 3000:3000 environment: - REDIS_HOSTredis redis: image: redis:7-alpine这个文件的意思很直白web 服务依赖 redis 服务web 里通过环境变量 REDIS_HOST 拿到 redis 的地址。相比一条一条执行 docker runcompose 把“一个完整环境有哪些进程、各自如何配置”都固化下来了——这和不写 Dockerfile、直接 docker commit 的思路完全是两个层次。5.3 多平台构建一台机器给不同“门店”出餐同一个 Dockerfile在 Intel 机器上构建出来是 x86_64 架构的镜像在 ARM 机器上构建出来是 arm64 架构的镜像。如果公司里有“一台研发机 一批 ARM 服务器”这种组合或者要同时发布到不同架构的云主机就需要用 buildx 做多平台构建。最简单的启用方式docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0.0 --push .这条命令会在一条流水线里同时构建两个架构的镜像并推送到仓库。它背后的技术是基于 QEMU 模拟器把构建过程模拟成目标架构环境。多平台构建踩坑点在于基础镜像必须也要支持目标架构。如果你依赖的基础镜像只有 x86 版本那 ARM 构建必然失败。好在这两年主流官方镜像基本都同步了多架构支持选镜像的时候留个心眼即可。最后再分享一个我自己的习惯在实际写 Dockerfile 这几年里我最大的体会是每次构建镜像都当成是给三个月后的自己留下的一张“配方卡”。如果哪一天线上环境出了问题要能顺着 Dockerfile 反推出当时的环境所有细节。所以我有一个坚持了很久的习惯每一个生产镜像的 Dockerfile都会在文件头部写清用途、维护人、最近变更记录。在团队协作里这个习惯特别有用——别人接手项目时不用来问我“这个镜像当时是怎么构建的”看 Dockerfile 就全明白了。另外还有一个今天没有展开但值得单独写一篇的小技巧如何利用docker history和docker inspect反推一个未知镜像的构建过程。遇到别人留下的黑盒镜像这两条命令往往比直接猜快得多。有兴趣的话可以先在自己构建的镜像上跑一下docker history --no-trunc看看输出效果对理解镜像分层会特别直观。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价