如何使用 docker buildx build 命令构建和管理镜像

DockerBeginner
立即练习

介绍

在本实验中,你将通过实践掌握使用 docker buildx build 命令构建和管理 Docker 镜像的技能。首先,你将使用默认设置构建一个简单镜像,学习如何通过 Dockerfile 定义镜像指令。

在掌握基础操作后,你将探索更高级的功能,包括使用构建参数(build arguments)以及针对多阶段构建中的特定阶段进行构建。你还将学习如何通过 --cache-from--cache-to 参数有效管理构建缓存以优化构建时间。此外,本实验将指导你构建多平台镜像并将其推送至镜像仓库,并演示如何在构建过程中安全地暴露密钥(secrets)和 SSH 代理(SSH agents)。

使用默认设置构建简单镜像

在本步骤中,你将学习如何使用 Dockerfile 构建一个简单的 Docker 镜像。Dockerfile 是一个文本文件,包含用户可以通过命令行调用的所有指令来组装镜像。Docker 可以通过读取 Dockerfile 中的指令自动构建镜像。

首先,导航到 ~/project 目录,这是本实验的工作目录。

cd ~/project

现在,让我们创建一个简单的 Dockerfile。这个 Dockerfile 将基于 ubuntu 基础镜像定义一个镜像,并在从该镜像运行容器时简单地打印 "Hello, Docker!"。

使用 nano 编辑器在 ~/project 目录下创建一个名为 Dockerfile 的文件。

nano Dockerfile

将以下内容添加到 Dockerfile 中:

FROM ubuntu:latest
CMD ["echo", "Hello, Docker!"]

让我们分解这个简单的 Dockerfile

  • FROM ubuntu:latest:该指令为我们的新镜像设置基础镜像。我们使用的是 Docker Hub 上官方 Ubuntu 镜像的最新版本。
  • CMD ["echo", "Hello, Docker!"]:该指令指定从该镜像启动容器时将执行的命令。在本例中,它将运行 echo 命令并输出 "Hello, Docker!"。

Ctrl + S 保存文件,然后按 Ctrl + X 退出 nano 编辑器。

现在我们有了 Dockerfile,可以使用 docker build 命令构建镜像。命令末尾的 . 告诉 Docker 在当前目录(~/project)中查找 Dockerfile。我们还将为镜像打上标签,例如 my-hello-image

docker build -t my-hello-image .

你将看到输出显示 Docker 正在逐层构建镜像。如果系统中尚未存在 ubuntu:latest 镜像,它将首先拉取该镜像,然后执行 CMD 指令。

构建完成后,你可以使用 docker images 命令列出可用镜像,以验证镜像是否创建成功。

docker images

你应该能在输出中看到 my-hello-image

最后,让我们从新构建的镜像运行一个容器,看看 CMD 指令的输出。

docker run my-hello-image

你应该能看到终端打印出 "Hello, Docker!"。这确认了我们的镜像构建正确,并且 CMD 指令按预期工作。

使用构建参数和目标阶段

在本步骤中,你将学习如何在 Dockerfile 中使用构建参数(ARG)和目标阶段来创建更灵活高效的构建。构建参数允许你向构建过程传递变量,而目标阶段则让你可以在单个 Dockerfile 中定义多个构建阶段,并仅构建特定阶段。

首先,确保你位于 ~/project 目录下。

cd ~/project

让我们修改现有的 Dockerfile,加入构建参数和一个简单的多阶段构建。我们将为问候消息定义一个参数,并使用第二个阶段从第一个阶段复制文件。

使用 nano 打开 Dockerfile

nano Dockerfile

将现有内容替换为以下内容:

## 阶段1:构建阶段
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo $GREETING > /app/greeting.txt

## 阶段2:最终阶段
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
CMD ["cat", "/greeting.txt"]

让我们理解这些修改:

  • ARG GREETING="Hello from build argument!":该指令定义了一个名为 GREETING 的构建参数,并设置了默认值。你可以在构建过程中覆盖这个默认值。
  • FROM ubuntu:latest as builder:这开始了第一个构建阶段并将其命名为 builder。这对多阶段构建很有用。
  • RUN echo $GREETING > /app/greeting.txt:在 builder 阶段,该命令会在 /app 目录下创建名为 greeting.txt 的文件,并将 GREETING 参数的值写入其中。
  • FROM ubuntu:latest:这开始了第二个构建阶段。默认情况下,这将是最终镜像。
  • COPY --from=builder /app/greeting.txt /greeting.txt:该指令从 builder 阶段(具体是从 /app/greeting.txt)复制 greeting.txt 文件到当前阶段的根目录(/)。这是多阶段构建中在阶段间传递构建产物的方式。
  • CMD ["cat", "/greeting.txt"]:这设置了从最终镜像运行容器时要执行的命令。它会打印 /greeting.txt 文件的内容。

保存 Dockerfile 并退出 nano

现在,让我们使用 docker build 命令构建镜像。我们将使用 --build-arg 标志为 GREETING 参数传递自定义值。我们还将这个镜像标记为 my-arg-image

docker build --build-arg GREETING="Greetings from the command line!" -t my-arg-image .

观察输出。你应该能看到构建过程使用了提供的构建参数。

构建完成后,列出镜像以确认 my-arg-image 存在。

docker images

现在,从 my-arg-image 运行一个容器查看输出。

docker run my-arg-image

你应该能看到打印出的 "Greetings from the command line!",这确认了构建参数已被使用。

接下来,让我们仅构建 builder 阶段。这对于创建包含构建产物但不包含最终应用的中间镜像很有用。我们使用 --target 标志指定阶段名称。

docker build --target builder -t my-builder-stage .

再次列出镜像。你现在应该能看到除了 my-arg-image 外还有 my-builder-stage

docker images

my-builder-stage 运行容器不会产生与 my-arg-image 相同的输出,因为最终阶段的 CMD 指令不包含在 builder 阶段中。让我们尝试运行它看看会发生什么(它可能会快速启动并退出,因为没有默认命令)。

docker run my-builder-stage

这演示了目标阶段如何让你控制将 Dockerfile 的哪部分构建到镜像中。

使用 --cache-from 和 --cache-to 管理构建缓存

在本步骤中,你将学习如何使用 --cache-from--cache-to 标志管理 Docker 构建缓存。构建缓存可以通过复用之前构建的层来显著加速后续构建。--cache-from 允许你指定一个镜像作为缓存源,而 --cache-to 则允许你将构建缓存导出到指定位置(如注册表或本地目录)。

首先,确保你位于 ~/project 目录下。

cd ~/project

让我们稍微修改 Dockerfile 来模拟通常会破坏缓存的更改。我们将添加一个简单的 RUN 指令。

使用 nano 打开 Dockerfile

nano Dockerfile

builder 阶段的 ARG 指令后添加以下行:

RUN echo "Adding a new layer"

更新后的 Dockerfile 应如下所示:

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo "Adding a new layer"
RUN echo $GREETING > /app/greeting.txt

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
CMD ["cat", "/greeting.txt"]

保存 Dockerfile 并退出 nano

现在,让我们不使用任何特定缓存选项再次构建镜像。Docker 会自动使用本地缓存(如果可用)。

docker build -t my-cached-image .

你应该会看到某些层是从头开始构建的,因为我们添加了新指令,使后续指令的缓存失效。

现在,让我们模拟一个场景:你可能希望使用之前构建的镜像作为缓存源,可能来自注册表或其他构建。出于演示目的,我们将使用刚刚构建的 my-cached-image 作为新构建的缓存源。

首先,让我们再次对 Dockerfile 做一个小改动来模拟另一个修改。

打开 Dockerfile

nano Dockerfile

更改新 RUN 指令中的消息:

RUN echo "Adding another new layer"

更新后的 Dockerfile 应如下所示:

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo "Adding another new layer"
RUN echo $GREETING > /app/greeting.txt

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
CMD ["cat", "/greeting.txt"]

保存并退出 nano

现在再次构建镜像,但这次使用 --cache-from 标志指定 my-cached-image 作为缓存源。

docker build --cache-from my-cached-image -t my-cached-image-from .

你应该会观察到 Docker 尝试使用来自 my-cached-image 的层。第一个 RUN 指令对应的层可能会因为指令更改而重新构建,但如果匹配,后续层可能会从缓存中拉取。

--cache-to 标志用于导出构建缓存。这在 CI/CD 流水线中特别有用,可以在构建之间共享缓存。要使用 --cache-to,通常需要使用支持缓存导出的构建驱动,例如 docker-container 驱动。

首先,让我们安装 Docker Compose,它通常与 buildx 一起使用。

sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

现在,让我们创建一个 buildx 构建器实例。

docker buildx create --use

现在,让我们构建镜像并将缓存导出到本地目录。我们将使用 local 缓存导出器。

docker buildx build --cache-to type=local,dest=./build-cache -t my-exported-cache-image . --load
  • docker buildx build:使用 buildx 工具进行构建
  • --cache-to type=local,dest=./build-cache:将缓存导出到当前目录下名为 build-cache 的本地目录
  • -t my-exported-cache-image:为生成的镜像打标签
  • .:指定构建上下文(当前目录)
  • --load:将构建的镜像加载到本地 Docker 镜像缓存中

你应该会看到指示构建和缓存导出的输出。~/project 目录下将创建一个 build-cache 目录。

现在,让我们模拟一个干净的构建环境,并尝试使用导出的缓存进行构建。首先,让我们删除我们构建的镜像。

docker rmi my-cached-image my-cached-image-from my-exported-cache-image

现在再次构建镜像,这次使用导出的缓存作为源。

docker buildx build --cache-from type=local,src=./build-cache -t my-imported-cache-image . --load
  • --cache-from type=local,src=./build-cache:从本地目录 build-cache 导入缓存

你应该会观察到 Docker 使用了来自导出缓存的层,与从头开始构建相比,显著加快了构建过程。

构建多平台镜像并推送至镜像仓库

在本步骤中,你将学习如何为多种架构(如 linux/amd64linux/arm64)构建 Docker 镜像,并将其推送至容器注册表。构建多平台镜像对于确保你的应用能在不同类型的硬件上运行至关重要。我们将使用你在上一步中初始化的 Docker Buildx 工具。

首先,确保你位于 ~/project 目录下。

cd ~/project

我们将使用现有的 Dockerfile 进行本步骤操作。让我们为 linux/amd64linux/arm64 平台构建镜像。我们将为镜像打上名称和版本标签,例如 your-dockerhub-username/my-multi-platform-image:latest请将 your-dockerhub-username 替换为你实际的 Docker Hub 用户名。 如果你没有 Docker Hub 账户,可以免费创建一个。

要为多平台构建,我们需在 docker buildx build 命令中使用 --platform 标志。同时需要使用 --push 标志将生成的清单列表和镜像推送至注册表。

docker buildx build --platform linux/amd64,linux/arm64 -t your-dockerhub-username/my-multi-platform-image:latest . --push

注意: 如果你尚未登录 Docker Hub,此命令会要求你登录。如需登录,可在另一个终端会话中使用 docker login 命令。

docker login

根据提示输入你的 Docker Hub 用户名和密码。

登录后,再次运行 docker buildx build 命令。

构建过程会比单平台构建耗时更长,因为它需要为每个指定架构构建镜像。构建完成后,Buildx 会创建一个引用各平台镜像的清单列表,并将所有内容推送至你指定的 Docker Hub 仓库。

你可以通过网页浏览器访问你的 Docker Hub 仓库来验证多平台镜像是否已推送。你应该能看到带有 latest 标签的 my-multi-platform-image,在「Tags」标签页下会显示它支持多种架构。

或者,你也可以使用 docker buildx imagetools inspect 命令来检查刚刚推送的清单列表。请将 your-dockerhub-username 替换为你的用户名。

docker buildx imagetools inspect your-dockerhub-username/my-multi-platform-image:latest

输出结果将显示清单列表及其指向的不同架构镜像。

这演示了如何构建和推送能在不同 CPU 架构上运行的镜像,使你的 Docker 镜像更具通用性。

在构建过程中使用密钥和 SSH 代理

在本步骤中,你将学习如何使用 Buildx 安全地将密钥和 SSH 代理暴露给 Docker 构建过程。这对于构建需要访问私有仓库、安装私有包或与需要认证的外部服务交互,但又不能将敏感信息直接嵌入 Dockerfile 的场景至关重要。

首先,确保你位于 ~/project 目录下。

cd ~/project

我们将修改 Dockerfile 来演示如何在构建过程中使用密钥。为此示例,我们将创建一个虚拟密钥文件并在构建时读取其内容。

~/project 目录下创建一个名为 mysecret.txt 的文件并写入密钥内容:

echo "This is a secret message!" > ~/project/mysecret.txt

现在用 nano 打开 Dockerfile

nano Dockerfile

builder 阶段添加一个新的 RUN 指令,使用 --mount=type=secret 标志来访问密钥:

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo "Adding another new layer"
RUN echo $GREETING > /app/greeting.txt
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /app/secret_content.txt

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
COPY --from=builder /app/secret_content.txt /secret_content.txt
CMD ["cat", "/greeting.txt", "/secret_content.txt"]

让我们理解这个新指令:

  • RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /app/secret_content.txt:该指令将密钥挂载到构建容器中
    • --mount=type=secret:指定我们正在挂载一个密钥
    • id=mysecret:这是我们在构建时将提供的密钥 ID
    • cat /run/secrets/mysecret:在构建容器内,密钥位于 /run/secrets/mysecret 路径下,我们使用 cat 命令读取其内容
    • > /app/secret_content.txt:我们将密钥内容重定向到构建器阶段 /app 目录下的 secret_content.txt 文件

我们还在最终阶段添加了一个 COPY 指令,用于从构建器阶段复制 secret_content.txt 文件。

保存 Dockerfile 并退出 nano

现在使用 docker buildx build 构建镜像,并通过 --secret 标志提供密钥:

docker buildx build --secret id=mysecret,src=~/project/mysecret.txt -t my-secret-image . --load
  • --secret id=mysecret,src=~/project/mysecret.txt:该标志向构建提供密钥
    • id=mysecret:与 Dockerfile 中指定的 ID 匹配
    • src=~/project/mysecret.txt:指定本地机器上密钥文件的路径

构建过程现在可以在执行 RUN --mount=type=secret... 指令时访问 mysecret.txt 的内容。密钥内容不会存储在最终镜像层中。

构建完成后,从镜像运行一个容器:

docker run my-secret-image

你应该能在控制台看到问候消息和密钥文件的内容。

现在,让我们演示如何将 SSH 代理暴露给构建。这在构建过程中克隆私有 Git 仓库时非常有用。

首先,确保你的 SSH 代理正在运行且已加载密钥。通常可以使用 ssh-add -l 检查。如果代理未运行或密钥未添加,你可能需要启动代理并添加密钥:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa ## 如果路径不同,请替换为你的 SSH 密钥路径

现在修改 Dockerfile 以使用 SSH 代理。我们将添加一个 RUN 指令,尝试使用 SSH 克隆一个(不存在的)私有仓库。

打开 Dockerfile

nano Dockerfile

builder 阶段添加一个新的 RUN 指令,使用 --mount=type=ssh 标志:

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo "Adding another new layer"
RUN echo $GREETING > /app/greeting.txt
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /app/secret_content.txt
RUN --mount=type=ssh git clone git@github.com:your-username/your-private-repo.git || echo "Skipping git clone as this is a demo"

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
COPY --from=builder /app/secret_content.txt /secret_content.txt
CMD ["cat", "/greeting.txt", "/secret_content.txt"]

注意:your-username/your-private-repo.git 替换为私有仓库 URL 的占位符。添加 || echo "Skipping git clone as this is a demo" 部分是为了在仓库不存在或你没有访问权限时构建不会失败。

保存 Dockerfile 并退出 nano

现在使用 docker buildx build 构建镜像,并通过 --ssh 标志提供 SSH 代理访问权限:

docker buildx build --ssh default -t my-ssh-image . --load
  • --ssh default:该标志将你的默认 SSH 代理暴露给构建

在构建过程中,RUN --mount=type=ssh... 指令将能够使用你的 SSH 代理与 Git 服务器进行身份验证。

构建完成后,你可以运行镜像,但由于 git clone 操作可能被跳过,输出将与之前相同:

docker run my-ssh-image

这演示了如何在 Docker 构建过程中使用 Buildx 安全地使用密钥和 SSH 代理,防止敏感信息被嵌入到镜像中。

总结

在本实验中,你学习了使用 Dockerfile 构建 Docker 镜像的基础知识。你首先创建了一个简单的 Dockerfile,定义了基于 Ubuntu 的镜像并执行基本命令。接着使用 docker build 命令构建该镜像,理解了 Docker 如何逐层处理 Dockerfile 中的指令,以及如何为生成的镜像打标签。

在基础知识之上,你探索了 docker buildx build 命令的更高级功能。这包括使用构建参数向构建过程传递动态值,以及利用多阶段构建中的目标阶段来优化镜像大小和构建时间。你还学习了如何通过 --cache-from--cache-to 有效管理构建缓存以加速后续构建。此外,你获得了构建多平台镜像的经验,使你的镜像能够在不同架构上运行,并将这些多平台镜像推送至容器注册表。最后,你掌握了如何在构建过程中安全地暴露密钥和使用 SSH 代理,从而提升镜像构建的安全性和灵活性。