3.3 Dockerfile 指令详解:装箱单写法


3.3 Dockerfile 指令详解:装箱单写法

本节摘要:Dockerfile 是镜像的装箱单——每条指令声明一层。本节逐条讲透核心指令的语义与陷阱,并重点讲层缓存机制:指令顺序决定重复构建的速度,写对顺序,构建从分钟级降到秒级。

为什么装箱单值得专门学

原理和规矩都齐了,进入主线任务的核心手艺:写装箱单。很多团队都有"能跑但很烂"的 Dockerfile——能跑是因为指令凑齐了,很烂是因为层缓存全废、体积翻倍、安全洞大开。本节的目标是让你懂每条指令的层效应,而不是背模板。

先认识一个最小可用的装箱单。假设当前目录有三个文件:应用代码 app.py、依赖清单 requirements.txt、配置目录 conf。装箱单如下:

# Dockerfile:一个 Python Web 应用的装箱单 FROM python:3.12-slim # 选底箱:精简版 Python 官方底座 WORKDIR /app # 设定箱内工作目录,后续指令都在这里 COPY requirements.txt . # 先只复制依赖清单(关键技巧,下文详解) RUN pip install --no-cache-dir -r requirements.txt COPY . . # 再复制应用代码(变动最频繁,放最后) EXPOSE 8000 # 声明对外泊位(文档性质,见下文) CMD ["python", "app.py"] # 箱子起吊时默认执行的命令

构建并验证:

# 构建镜像:-t 打标签,末尾的点是构建上下文(把当前目录交给引擎) docker build -t myapp:0.1 . # 输出(节选): # [4/6] RUN pip install --no-cache-dir -r requirements.txt # [5/6] COPY . . # [6/6] CMD ["python", "app.py"] # naming to docker.io/library/myapp:0.1 done # 起吊验证 docker run -d --name myapp-test -p 8000:8000 myapp:0.1 docker ps --filter name=myapp-test # STATUS 为 Up 即装箱合格

核心指令逐条过堂

FROM:选底箱。 所有镜像都要踩在某个底座上;底座的选型直接决定体积与安全面。常用三档:完整发行版(如 ubuntu,几百 MB)、精简版(slim 后缀,几十 MB)、极简系统(alpine,约五 MB,但 libc 实现不同,个别依赖需要额外适配)。选型原则:能用 slim 不用完整版,依赖兼容性踩坑时退回完整版。另外 FROM 可以多次出现——那是 3.4 节多阶段构建的伏笔。

RUN:构建时干活。 在构建阶段执行命令并制造新层,主要用于装依赖。两条铁律:其一,同属一件事的命令用 && 串成一条 RUN(少制造层);其二,临时产物在同一条指令里用完即删(跨指令删除无效,原因见 3.4)。pip 的 --no-cache-dir 就是干这个的——不把下载缓存留在层里。

COPY 与 ADD:往箱里放货。 COPY 只做复制,语义清晰;ADD 额外支持自动解压与远程地址,行为复杂易误用,日常一律用 COPY,ADD 只在明确需要解压时出场。还有一位隐形成员 .dockerignore——把构建不需要的东西(本地缓存、密钥文件、历史日志)挡在构建上下文之外,与 COPY 配合才能又快又安全。

CMD 与 ENTRYPOINT:起吊后干什么。 两者都定义容器启动命令,语义分工不同:CMD 给默认值(可被 run 命令行参数整个替换),ENTRYPOINT 定固定入口(命令行参数变成它的追加参数)。最佳实践是两者配合——入口写死程序,默认参数交给 CMD:

# 入口与默认参数的分工:程序固定,参数可换 ENTRYPOINT ["python", "app.py"] CMD ["--port", "8000"] # 效果演示(命令行行为,非文件内容): # docker run myapp -> 实际执行:python app.py --port 8000 # docker run myapp --port 9000 -> 实际执行:python app.py --port 9000

WORKDIR、ENV、EXPOSE:环境声明三件套。 WORKDIR 设工作目录(之后的相对路径都以它为基准,也免去一串 cd);ENV 设环境变量(配置注入的标准通道,12-factor 应用的基础设施);EXPOSE 声明监听端口——注意它是文档与约定,不负责真正的对外发布(发布靠 run 的 -p 泊位分配),它的实际用途是让协议级互联与工具识别端口。

层缓存:顺序就是性能

层缓存是 Dockerfile 最值钱的机制:构建时若某条指令的输入没变(指令内容、涉及文件、基础镜像),引擎直接复用上次的缓存层,跳过执行。缓存自上而下逐层检查,一条失效,其后全部重来。 于是得出装箱单的黄金顺序:从最稳定到最易变排列

对比看一组真实差异。反例——把代码复制放在依赖安装之前:

# 反例:代码一改,依赖重装(每次构建都最慢路径) COPY . . # 代码与依赖清单一起复制 RUN pip install -r requirements.txt # 清单没变,但上面一层变了,缓存失效,重装

正例——把依赖安装提到代码复制之前:

# 正例:改代码只触发最后一层重建 COPY requirements.txt . # 清单不常变 RUN pip install --no-cache-dir -r requirements.txt # 清单不变则缓存命中,秒过 COPY . . # 代码层独立,随便改

用构建输出验证缓存命中(第二次构建时):

# 第二次构建(只改了代码文件)的输出(节选) docker build -t myapp:0.2 . # [2/5] WORKDIR /app CACHED # [3/5] COPY requirements.txt . CACHED # [4/5] RUN pip install ... CACHED <- 依赖层命中缓存,秒过 # [5/5] COPY . . 0.8s <- 只有代码层真正重建

依赖安装动辄几十秒甚至几分钟,代码复制通常不到一秒——顺序写对,日常构建提速一个数量级,这就是本节标题"装箱单写法"里最值钱的一句。

本节要点回顾

  • Dockerfile 是层的声明序列:每条指令制造一层,指令顺序影响缓存与体积。
  • 底座三档:完整版、slim、alpine;默认选 slim,兼容性出问题再回退。
  • RUN 合并同类命令、临时产物用完即删;COPY 优先于 ADD;.dockerignore 挡住无关货。
  • CMD 是默认参数可被替换,ENTRYPOINT 是固定入口;两者配合最规范。
  • 层缓存自上而下逐层检查,一条失效其后全重来;稳定指令在上、易变指令在下

会写装箱单了,但箱子里还塞着生产废料——下一节教你减重:多阶段构建与瘦身清单。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U