本节摘要:Buildah 提供两条构建路线:bud 子命令兼容 Dockerfile(含多阶段构建),from/run/commit 三板斧提供纯脚本化构建。前者声明式、缓存友好;后者命令式、可调试、可做 Dockerfile 做不到的事(挂载宿主目录构建、逐步交互修正)。本节完整走一遍两条路线,讲清缓存机制,最后给出按场景选路线的判断框架。
两条路线的关系先用一张图定位,后面再逐条展开:

Buildah 完全兼容 Dockerfile 语法(含多阶段构建),对既有项目零迁移成本:
# 项目里已有 Dockerfile,直接构建 buildah bud -t myapp:buildah . # STEP 1/8: FROM docker.io/library/golang:1.22-alpine AS build # STEP 2/8: WORKDIR /src # ...(逐条指令回显,与 docker build 体验一致) # 多阶段构建同样支持 buildah bud -t myapp:multi --file Dockerfile.multistage . # 构建产物与 docker build 的输出镜像 bit 级兼容(都遵守 OCI 标准)
差别在幕后:docker build 把构建上下文发给 dockerd 由它在特权侧执行;buildah bud 在用户命名空间内逐指令执行,全程无特权进程参与(第 3.1 节的安全论证在这里落地)。构建缓存也更有意思——它的缓存键机制与 Docker 同源:每条指令的文本与其父层状态共同决定缓存命中。所以下面两条经验在两个引擎里同样成立:把变化频繁的层(源码 COPY)放后面,把稳定层(系统依赖安装)放前面;RUN 指令合并要克制,合并省层数但击穿缓存的代价更大。
脚本化构建的核心是"工作容器":一个可以边跑边改的容器,改满意了再凝固成镜像。
# 第一步:from——从基础镜像创建工作容器 buildah from alpine:3.19 # alpine-working-container # 第二步:run——在工作容器里执行命令(注意是 buildah run,不是容器内交互) buildah run alpine-working-container -- apk add --no-cache python3 py3-pip buildah run alpine-working-container -- pip3 install --no-cache-dir flask # 把宿主机的应用代码拷进去(buildah 专属能力:无需 COPY 指令语义) buildah copy alpine-working-container ./app /opt/app # 配置与元数据 buildah config --entrypoint '["python3","/opt/app/server.py"]' \ --port 8000 --env APP_ENV=prod \ alpine-working-container # 第三步:commit——凝固成镜像 buildah commit alpine-working-container myapp:scripted # ... Getting image source signatures # myapp:scripted 创建完成,podman images 立即可见(第 3.2 节的共享存储) # 收尾:删除工作容器 buildah rm alpine-working-container
三板斧的价值不在"换种写法",在于它解开的三把锁。第一把:可调试。Dockerfile 构建失败只能看回显猜原因;工作容器模式下构建失败时容器还在,直接 buildah run 容器名 -- sh 进去复现那条命令,排错像排普通脚本。第二把:可挂载。buildah run --volume 能把宿主目录挂进构建过程——Dockerfile 的 RUN 做不到(构建容器不便挂载宿主路径),这让"用宿主机的编译缓存加速构建"这类需求有了官方答案。第三把:可交互。配合 buildah run 容器名 -- bash 可以人工试装依赖,试通了再把命令写进正式脚本——构建过程本身可以"探索式开发"。
对比驱动的好场景:目标是一个只含一个静态二进制的镜像。Dockerfile 路线的标准答案是多阶段构建;脚本路线的答案是 scratch 起步:
# 脚本路线:从零开始,只放一个二进制 buildah from scratch # working-container buildah copy working-container ./hello-static /hello buildah config --entrypoint '["/hello"]' working-container buildah commit working-container hello:minimal # 最终镜像约等于二进制文件大小(1–2 MB 量级), # 没有发行版、没有 shell、攻击面趋近于零 # 对比:多阶段 Dockerfile 同样能做小,但语法门槛更高: # 需要理解 FROM ... AS、COPY --from=build 两个语法点。 # 两条路线产物等价,差别是"脚本里每步可见" 与 "声明里一次成型"
安全视角补一句:无 shell 的镜像被入侵后的利用难度显著上升(没有 sh 可执行,攻击者连盲执行都费劲),这也是第 6 章纵深防御的镜像侧贡献。
| 场景 | 推荐路线 | 理由 |
|---|---|---|
| 既有 Dockerfile 项目 | bud 兼容路线 | 零迁移成本,缓存行为与团队认知一致 |
| CI 流水线 | bud 或脚本 | 无特权构建两者皆可;需要挂载缓存时选脚本 |
| 构建过程复杂多分支 | 脚本路线 | bash 的 if/for 比 Dockerfile 的间接手段自然 |
| 追求最小镜像 | scratch 脚本 | 步骤透明,产物审计直观 |
| 团队协作、多语言项目 | Dockerfile | 声明式的低门槛与生态惯性 |
混合策略是被低估的选项:同一仓库里基础镜像用脚本构建(发挥挂载缓存与交互调试),应用镜像用 Dockerfile(团队可读性),CI 里两者串联。构建路线不是站队问题,是工序安排问题。