☰
Buildah 入门实战:从 scratch 到 Dockerfile,构建可移植 OCI 镜像的完整工作流
2026/9/25 17:54:01 网站建设 项目流程
  • 云原生

【免费下载链接】buildah

A tool that facilitates building OCI images.

项目地址:https://gitcode.com/gh_mirrors/bu/buildah
点击查看免费下载

本文基于 Buildah 官方入门教程(docs/tutorials/01-intro.md)整理并深入扩展,带你完整走一遍 Buildah 构建 OCI 容器镜像的核心工作流:安装与验证、基于现有镜像创建 working container、从scratch空镜像逐层填充内容、提交与配置元数据,以及使用 Dockerfile/Containerfile 构建镜像。读完之后,你将掌握 Buildah CLI 的典型操作序列,并理解每个命令背后对应的源码实现与底层库(containers/image、containers/storage)的协作关系。

一、Buildah 与 OCI 镜像规范

Buildah 的目标是构建符合 OCI 镜像规范 的容器镜像。镜像既可以基于现有镜像扩展,也可以完全从空白(scratch)开始,还可以直接由 Dockerfile 驱动构建。

Buildah 的镜像能力建立在两个基础库之上:

  • containers/image:提供镜像的复制(push、pull)、检视(inspect)与签名(sign)机制;
  • containers/storage:提供文件系统层(layers)、容器镜像以及容器本身的存储机制。

Buildah 本身是一个 CLI,它利用上述两个库来构建、移动和管理容器镜像与工作容器。由于产物严格遵循 OCI 标准,用 Buildah 构建的镜像可以在 Docker 等其他容器环境中运行。

需要明确的适用边界:

  • Buildah 支持多种 Linux 发行版,但不支持 Windows 或 macOS;
  • Buildah 专注于构建OCI 镜像,而 Podman 提供覆盖面更广的命令集(维护、修改、运行镜像与容器)。二者同属 containers 生态,定位互补。

二、安装 Buildah

官方入门教程以使用dnf包管理器的 Linux 发行版为前提,安装步骤如下(安装包需要 root 权限):

$ sudo -s # dnf -y install buildah

仓库中的 install.md 给出了更多发行版的安装方式,包括:

  • Debian/Ubuntu:sudo apt-get -y install buildah;
  • Fedora:sudo dnf -y install buildah;
  • CentOS:sudo yum -y install buildah;
  • openSUSE:sudo zypper install buildah;
  • Arch Linux:sudo pacman -S buildah;
  • Gentoo:sudo emerge app-containers/buildah。

除了发行版差异,还有两个关键前提值得注意:

  1. 内核要求:RHEL/CentOS 上要求 7.4 及以上内核;其他发行版需要支持 OverlayFS 或 fuse-overlayfs 的内核;
  2. runc 依赖:buildah run执行命令、或buildah build遇到RUN指令时,Buildah 依赖runc来真正运行进程。通过 yum/dnf/apt 安装 Buildah 时通常会顺带装好 runc。

Rootless(免 root)运行

如果计划以非 root 用户(rootless user)运行 Buildah,系统管理员可能需要预先做额外配置。Buildah 对 rootless 用户的配置要求与 Podman 完全一致,可参考 Podman 官方的 rootless tutorial。仓库中的 buildah-unshare.1.md 则说明了后文会用到的buildah unshare命令:它用于创建并进入 user namespace 与 mount namespace。

三、安装后验证:images、containers 与 working container

安装完成后,先确认本地存储是空的。buildah images列出所有镜像,buildah containers列出所有 working container:

# buildah images # buildah containers

此时两条命令都应该没有输出。接下来创建第一个 working container:

# container=$(buildah from fedora)

这里有两点值得深入理解。

第一,buildah from的输出可以直接赋给 shell 变量。从源码看,cmd/buildah/from.go 中fromCmd在成功构建Builder之后执行fmt.Printf("%s\n", builder.Container),把容器名打印到标准输出——这正是教程中container=$(buildah from fedora)能够捕获名称的机制。

第二,working container 的命名规则。Buildah 默认会在基础镜像名后追加-working-container后缀来生成容器名,例如fedora-working-container。命名逻辑位于 new.go:

name := "working-container" if options.ContainerSuffix != "" { name = options.ContainerSuffix } if options.Container != "" { name = options.Container } else { if imageSpec != "" { name = imageNamePrefix(imageSpec) + "-" + name } }

其中imageNamePrefix(new.go)会去掉镜像名中的 tag、digest(截断到 12 位)、@与/部分,只保留最后一段镜像名,从而得到fedora这样的前缀。如果同名容器已存在,findUnusedContainer会进一步追加编号避免冲突。

查看变量内容并进入容器执行命令:

# echo $container fedora-working-container # buildah run $container bash

执行后会看到一个新的 shell 提示符,说明 bash 正在容器内运行。注意buildah run的定位:主要用于调试和构建过程中的命令执行。如果要在生产环境长期运行容器,更适合使用 Podman 或 CRI-O 这类完整的容器运行时。

buildah run中的--分隔符

退出容器后,教程演示了"容器内缺软件再装软件"的场景:

# buildah run $container java

容器里没装 Java,会看到类似这样的报错:

runc create failed: unable to start start container process: exec: "java": executable file not found in $PATH

于是需要在容器里安装 Java:

# buildah run $container -- dnf -y install java

这里的--语法告诉 Buildah:其后不再解析buildah run自身的选项,--之后的所有内容都作为容器内命令的参数。当你要执行的容器内命令自身携带选项(如dnf -y install)时,使用--是必要的,可以防止选项被 Buildah 误吞。

之后再次运行buildah run $container java将输出 Java 的标准Usage信息,说明安装成功。

四、从 scratch 构建:真正"从零"的镜像

Buildah 的一个重要能力是从空白构建镜像:可以精确控制镜像内容,剔除生产环境不需要的组件(比如大多数生产镜像并不需要dnf这样的包管理器)。

4.1 创建空容器

特殊的"镜像名"scratch告诉 Buildah 创建一个空容器——它只带有少量元数据,没有任何实际的 Linux 内容:

# newcontainer=$(buildah from scratch) # buildah containers

输出类似:

CONTAINER ID BUILDER IMAGE ID IMAGE NAME CONTAINER NAME 82af3b9a9488 * 3d85fcda5754 docker.io/library/fedora:latest fedora-working-container ac8fa6be0f0a * scratch working-container

注意两点:

  • 空容器的默认名字就是working-container(对应 new.go 中的默认值,没有镜像名可作前缀);
  • 运行buildah images时看不到名为scratch的镜像——scratch只是一个特殊值,表示该 working container 并非基于任何镜像,"从 nothing 开始"。

此时在容器里执行buildah run $newcontainer bash会失败——容器里连 bash、dnf 都没有,它本质上只是内核之上的一层空文件系统。

4.2 用 buildah mount 暴露容器根文件系统

要把内容"塞进"这个空容器,需要buildah mount命令:

# scratchmnt=$(buildah mount $newcontainer) # echo $scratchmnt /var/lib/containers/storage/overlay/b78d0e11957d15b5d1fe776293bd40a36c28825fb6cf76f407b4d0a95b2a200d/merged

输出的路径是一个overlay 挂载点,它就是容器使用的根文件系统。以 root 运行时,overlay 挂载点位于/var/lib/containers/storage之下;rootless 模式下则位于家目录的.local/share/containers/storage之下——这正体现了底层 containers/storage 库的存储布局。

从源码看,buildah mount最终调用 mount.go 中的Builder.Mount:

func (b *Builder) Mount(label string) (string, error) { mountpoint, err := b.store.Mount(b.ContainerID, label) if err != nil { return "", fmt.Errorf("mounting build container %q: %w", b.ContainerID, err) } b.MountPoint = mountpoint err = b.Save() if err != nil { return "", fmt.Errorf("saving updated state for build container %q: %w", b.ContainerID, err) } return mountpoint, nil }

可以看到它委托给存储驱动(b.store.Mount)完成实际的 overlay 挂载,并把挂载点持久化到 Builder 状态(b.Save()),供后续buildah unmount时清理。

rootless 注意事项:rootless 模式下直接执行buildah mount会失败,因为挂载容器必须在你自己拥有的 mount namespace 中进行。正确的做法是先用buildah unshare创建并进入 user namespace 与 mount namespace,再执行 mount,并且由于 shell 环境变了,需要把变量导出:

$ export newcontainer $ buildah unshare # scratchmnt=$(buildah mount $newcontainer)

4.3 用 dnf installroot 向容器填充软件

拿到挂载点之后,就可以在宿主机上直接"装包进容器"。教程以安装bash与coreutils为例(换成nginx等任何需要的包也一样):

# dnf install --installroot $scratchmnt --releasever 42 bash coreutils --use-host-config --setopt "*.countme=false" --setopt installweak_deps=false -y

关键参数是--installroot $scratchmnt:让 dnf 把包装进挂载目录而不是宿主机。教程特别提示:示例中的--releasever 42对应 Fedora 42,这个版本值必须对宿主机上的 dnf 有效——例如在 RHEL 平台上应写--releasever 8.1之类的有效版本。若希望容器最终基于某个特定发行版,可把buildah from scratch换成buildah from fedora,并使用该平台的版本值。

验证一下容器内确实有了/usr/bin:

# buildah run $newcontainer sh sh-5.1# cd /usr/bin sh-5.1# ls sh-5.1# exit

4.4 copy、config、commit:完成第一张镜像

在宿主机上创建一个可执行脚本runecho.sh:

#!/usr/bin/env bash for i in `seq 0 9`; do echo "This is a new container from ipbabble [" $i "]" done
# chmod +x runecho.sh

然后三步完成镜像:

# buildah copy $newcontainer ./runecho.sh /usr/bin/ # buildah config --cmd /usr/bin/runecho.sh $newcontainer # buildah commit $newcontainer newimage

这一步值得理解buildah run与podman run的本质区别,教程给出的类比非常准确:

  • buildah run等价于 Dockerfile 中的RUN:它永远需要你显式告诉它要运行什么命令;
  • podman run等价于docker run:它可以读取镜像配置(即上一步buildah config --cmd写入的默认命令)来决定运行什么。

先用 Buildah 直接指定命令验证脚本可执行:

# buildah run $newcontainer /usr/bin/runecho.sh This is a new container from ipbabble [ 0 ] This is a new container from ipbabble [ 1 ] ... This is a new container from ipbabble [ 9 ]

再用 Podman 基于新镜像起一个全新容器,不指定命令,它会自动执行镜像配置里的--cmd:

# dnf -y install podman # 先安装 # podman run --rm newimage This is a new container from ipbabble [ 0 ] ... This is a new container from ipbabble [ 9 ]

输出一致,说明这个从 scratch 构建的镜像完全可用。

4.5 元数据、二次 commit 与清理

继续为工作容器补充元数据:

# buildah config --created-by "ipbabble" $newcontainer # buildah config --author "wgh at redhat.com @ipbabble" --label name=fedora42-bashecho $newcontainer # buildah inspect $newcontainer

注意一个容易踩的坑:元数据修改发生在上一次 commit 之后,所以必须再次 commit 才能得到包含新元数据的镜像:

# buildah unmount $newcontainer # buildah commit $newcontainer fedora-bashecho # buildah images

此时会看到新镜像localhost/fedora-bashecho:latest。检查镜像元数据用--type=image:

# buildah inspect --type=image fedora-bashecho

之后每次需要基于该镜像起容器,直接buildah from fedora-bashecho即可。工作容器已完成使命,可以删除(按变量或按名字等价):

# buildah rm $newcontainer # buildah rm working-container

五、可移植性:把镜像推给 Docker daemon

教程用一个实验证明 Buildah 产出的 OCI 镜像是标准、可移植的:安装并启动 Docker,然后把镜像从 containers/storage 的存储区复制到 Docker daemon 的存储区(/var/lib/docker):

# dnf -y install docker # systemctl start docker # buildah push fedora-bashecho docker-daemon:fedora-bashecho:latest # docker run --rm fedora-bashecho This is a new container from ipbabble [ 0 ] ... This is a new container from ipbabble [ 9 ]

几个要点:

  • docker-daemon:是显式的 transport 前缀。docker://(默认,指向 Registry HTTP API V2)、docker-daemon:(指向本地 Docker daemon 的内部存储)、dir:、oci:、oci-archive:、docker-archive:等 transport 的完整定义见 buildah-from.1.md 与 buildah-push.1.md;
  • 底层机制:containers/image 库调用 containers/storage 库从 Buildah 的存储位置读出镜像内容,发送给本地 Docker daemon 写入其存储。教程提醒这一步通常用不到——用 Buildah 的人多半不用 Docker,这里仅作可移植性演示;
  • 架构差异:Docker 必须依赖一个常驻 daemon 进程才能执行任何客户端命令;Buildah 与 Podman 则没有 daemon 依赖,命令直接操作存储与命名空间。

演示完成后可以dnf -y remove docker收尾。

六、使用 Containerfile/Dockerfile 构建镜像

如果你已有现成的 Dockerfile 资产,Buildah 可以无缝复用:build命令接受 Dockerfile 作为输入,产出 OCI 镜像。教程给出的示例 Dockerfile 如下:

# Base on the most recently released Fedora FROM fedora:latest MAINTAINER ipbabble email buildahboy@redhat.com # not a real email # Install updates and httpd RUN echo "Updating all fedora packages"; dnf -y update; dnf -y clean all RUN echo "Installing httpd"; dnf -y install httpd && dnf -y clean all # Expose the default httpd port 80 EXPOSE 80 # Run the httpd CMD ["/usr/sbin/httpd", "-DFOREGROUND"]

执行构建:

# buildah build -f Dockerfile -t fedora-httpd .

由于buildah build默认使用当前目录下的Dockerfile并以当前目录作为构建上下文,上式可以简写为:

# buildah build -t fedora-httpd

构建过程中你会看到 Dockerfile 的每一步依次执行(FROM会先创建一个以<镜像名>-working-container命名的工作容器,RUN指令经由 runc 在容器内执行——这与前面buildah run的机制同源),完成后buildah images中出现新镜像。

用 Podman 起容器验证服务,并做端口映射:

# podman run --rm -p 8123:80 fedora-httpd

另开一个 shell:

# curl localhost:8123

看到标准的 Apache 欢迎页即验证成功。教程最后的开放练习:修改 Dockerfile,不安装 httpd,改用ADD指令引入前面的runecho.sh并把它设为CMD,再走一遍构建流程。

仓库的 tests/ 目录中有大量围绕 Dockerfile 行为的集成测试(如 tests/bud.bats 及各tests/bud/子目录的测试用例),可以结合源码进一步理解buildah build对COPY、ADD、多阶段构建、ARG/ENV等指令的具体处理逻辑。

七、小结:一条完整的 Buildah 心智模型

把上面的操作串起来,Buildah 的核心工作模型是四步:

  1. buildah from <image|scratch>:创建 working container(源码入口 cmd/buildah/from.go,命名规则见 new.go);
  2. 填充内容:buildah run(在容器内执行命令)、buildah copy(拷入文件)、buildah mount+ 宿主工具(直接操作根文件系统,实现见 mount.go);
  3. buildah config:写入元数据(cmd、label、author 等);
  4. buildah commit:把工作容器的变更固化为 OCI 镜像,之后可通过buildah push发送到 registry、Docker daemon 等任意 transport。

所有产物严格遵循 OCI 规范,因此天然可移植。若想继续深入,建议按序阅读 docs/tutorials/ 下的后续教程(镜像仓库交互、ONBUILD 机制、把 Buildah 作为库集成进自有构建工具、rootless OpenShift 构建),以及各命令的 man page(docs/buildah-run.1.md、docs/buildah-commit.1.md、docs/buildah-mount.1.md、docs/buildah-config.1.md、docs/buildah-push.1.md 等),它们覆盖了本教程未展开的全部命令行参数。

  • 云原生

【免费下载链接】buildah

A tool that facilitates building OCI images.

项目地址:https://gitcode.com/gh_mirrors/bu/buildah
点击查看免费下载
上一篇:Apache Pulsar 授权机制详解:Authorization 配置、Superusers 与 Proxy Roles 实战指南
下一篇:Lynx 模板二进制编码中的 Style Object 解析与编码实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询