构建上下文

docker builddocker buildx build 命令从 Dockerfile 和上下文构建 Docker 镜像。

什么是构建上下文?

构建上下文是您的构建可以访问的一组文件。 您传递给构建命令的位置参数指定了您希望用于构建的上下文:

$ docker build [OPTIONS] PATH | URL | -
                         ^^^^^^^^^^^^^^

您可以将以下任何输入作为构建的上下文传递:

  • 本地目录的相对或绝对路径
  • Git 仓库、压缩包或纯文本文件的远程 URL
  • 一个纯文本文件或通过标准输入传输到docker build命令的压缩包

文件系统上下文

当你的构建上下文是一个本地目录、远程Git仓库或tar文件时,这些文件就成为构建器在构建过程中可以访问的文件集。构建指令如COPYADD可以引用上下文中的任何文件和目录。

文件系统构建上下文是递归处理的:

  • 当您指定本地目录或压缩包时,所有子目录都会被包含
  • 当您指定一个远程Git仓库时,仓库及其所有子模块都会被包含

有关可以与构建一起使用的不同类型的文件系统上下文的更多信息,请参阅:

文本文件上下文

当您的构建上下文是一个纯文本文件时,构建器会将该文件解释为Dockerfile。使用这种方法,构建不会使用文件系统上下文。

欲了解更多信息,请参阅 空的构建上下文

本地上下文

要使用本地构建上下文,您可以指定一个相对或绝对文件路径给docker build命令。以下示例显示了一个使用当前目录(.)作为构建上下文的构建命令:

$ docker build .
...
#16 [internal] load build context
#16 sha256:23ca2f94460dcbaf5b3c3edbaaa933281a4e0ea3d92fe295193e4df44dc68f85
#16 transferring context: 13.16MB 2.2s done
...

这使得当前工作目录中的文件和目录对构建器可用。构建器在需要时从构建上下文中加载所需的文件。

你也可以使用本地压缩包作为构建上下文,通过将压缩包内容传输到docker build命令。参见Tarballs

本地目录

考虑以下目录结构:

.
├── index.ts
├── src/
├── Dockerfile
├── package.json
└── package-lock.json

如果你将此目录作为上下文传递,Dockerfile 指令可以在构建中引用并包含这些文件。

# syntax=docker/dockerfile:1
FROM node:latest
WORKDIR /src
COPY package.json package-lock.json .
RUN npm ci
COPY index.ts src .
$ docker build .

使用标准输入的Dockerfile的本地上下文

使用以下语法通过本地文件系统中的文件构建镜像,同时使用来自标准输入的Dockerfile。

$ docker build -f- <PATH>

语法使用 -f(或 --file)选项来指定要使用的 Dockerfile,并使用连字符(-)作为文件名,指示 Docker 从标准输入读取 Dockerfile。

以下示例使用当前目录(.)作为构建上下文,并通过使用here-document传递的Dockerfile构建镜像。

# create a directory to work in
mkdir example
cd example

# create an example file
touch somefile.txt

# build an image using the current directory as context
# and a Dockerfile passed through stdin
docker build -t myimage:latest -f- . <<EOF
FROM busybox
COPY somefile.txt ./
RUN cat /somefile.txt
EOF

本地压缩包

当你将一个tarball传递给构建命令时,构建会使用tarball的内容作为文件系统上下文。

例如,给定以下项目目录:

.
├── Dockerfile
├── Makefile
├── README.md
├── main.c
├── scripts
├── src
└── test.Dockerfile

你可以创建一个目录的 tarball 并将其通过管道传输到构建中以用作上下文:

$ tar czf foo.tar.gz *
$ docker build - < foo.tar.gz

构建从 tarball 上下文中解析 Dockerfile。您可以使用 --file 标志来指定 Dockerfile 的名称和位置,相对于 tarball 的根目录。以下命令使用 tarball 中的 test.Dockerfile 进行构建:

$ docker build --file test.Dockerfile - < foo.tar.gz

远程上下文

您可以指定远程Git仓库、压缩包或纯文本文件的地址作为您的构建上下文。

  • 对于Git仓库,构建器会自动克隆仓库。参见 Git 仓库
  • 对于tarballs,构建器会下载并提取tarball的内容。 参见 Tarballs

如果远程压缩包是一个文本文件,构建器不会接收到 文件系统 上下文,而是假设远程 文件是一个Dockerfile。请参阅 空构建上下文

Git 仓库

当你将一个指向Git仓库位置的URL作为参数传递给docker build时,构建器会将该仓库用作构建上下文。

构建器执行仓库的浅克隆,仅下载HEAD提交,而不是整个历史记录。

构建器递归地克隆仓库及其包含的任何子模块。

$ docker build https://github.com/user/myrepo.git

默认情况下,构建器会克隆您指定的仓库默认分支上的最新提交。

URL片段

您可以将URL片段附加到Git仓库地址,以使构建器克隆仓库的特定分支、标签和子目录。

URL片段的格式是#ref:dir,其中:

  • ref 是分支、标签或提交哈希的名称
  • dir 是仓库内的一个子目录

例如,以下命令使用container分支,以及该分支中的docker子目录作为构建上下文:

$ docker build https://github.com/user/myrepo.git#container:docker

下表列出了所有有效的后缀及其构建上下文:

Build Syntax SuffixCommit UsedBuild Context Used
myrepo.gitrefs/heads/<default branch>/
myrepo.git#mytagrefs/tags/mytag/
myrepo.git#mybranchrefs/heads/mybranch/
myrepo.git#pull/42/headrefs/pull/42/head/
myrepo.git#:myfolderrefs/heads/<default branch>/myfolder
myrepo.git#master:myfolderrefs/heads/master/myfolder
myrepo.git#mytag:myfolderrefs/tags/mytag/myfolder
myrepo.git#mybranch:myfolderrefs/heads/mybranch/myfolder

当你在URL片段中使用提交哈希作为ref时,请使用完整的40字符SHA-1哈希字符串。不支持短哈希,例如截断为7字符的哈希。

# ✅ The following works:
docker build github.com/docker/buildx#d4f088e689b41353d74f1a0bfcd6d7c0b213aed2
# ❌ The following doesn't work because the commit hash is truncated:
docker build github.com/docker/buildx#d4f088e

保留 .git 目录

默认情况下,BuildKit 在使用 Git 上下文时不会保留 .git 目录。 你可以通过设置 BUILDKIT_CONTEXT_KEEP_GIT_DIR 构建参数 来配置 BuildKit 保留该目录。 如果你希望在构建过程中获取 Git 信息,这可能很有用:

# syntax=docker/dockerfile:1
FROM alpine
WORKDIR /src
RUN --mount=target=. \
  make REVISION=$(git rev-parse HEAD) build
$ docker build \
  --build-arg BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
  https://github.com/user/myrepo.git#main

私有仓库

当您指定一个Git上下文,并且它也是一个私有仓库时,构建器需要您提供必要的身份验证凭据。您可以使用SSH或基于令牌的身份验证。

如果指定的Git上下文是SSH或Git地址,Buildx会自动检测并使用SSH凭证。默认情况下,这会使用$SSH_AUTH_SOCK。你可以通过--ssh 标志来配置要使用的SSH凭证。

$ docker buildx build --ssh default git@github.com:user/private.git

如果你想使用基于令牌的认证,你可以通过 --secret 标志传递令牌。

$ GIT_AUTH_TOKEN=<token> docker buildx build \
  --secret id=GIT_AUTH_TOKEN \
  https://github.com/user/private.git

注意

不要使用 --build-arg 来传递秘密信息。

从标准输入获取Dockerfile的远程上下文

使用以下语法通过本地文件系统中的文件构建镜像,同时使用来自标准输入的Dockerfile。

$ docker build -f- <URL>

语法使用 -f(或 --file)选项来指定要使用的 Dockerfile,并使用连字符(-)作为文件名,指示 Docker 从标准输入读取 Dockerfile。

这在您想要从没有包含Dockerfile的仓库构建镜像的情况下非常有用。或者如果您想要使用自定义的Dockerfile进行构建,而不需要维护自己的仓库分支。

以下示例使用来自标准输入的Dockerfile构建镜像,并从hello-world仓库中添加hello.c文件。

docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c ./
EOF

远程压缩包

如果你传递一个远程压缩包的URL,URL本身会被发送到构建器。

$ docker build http://server/context.tar.gz
#1 [internal] load remote build context
#1 DONE 0.2s

#2 copy /context /
#2 DONE 0.1s
...

下载操作将在运行BuildKit守护程序的主机上执行。请注意,如果您使用的是远程Docker上下文或远程构建器,那不一定与您发出构建命令的机器相同。BuildKit会获取context.tar.gz并将其用作构建上下文。Tarball上下文必须是符合标准tar Unix格式的tar存档,并且可以使用xzbzip2gzipidentity(无压缩)格式中的任何一种进行压缩。

空上下文

当你使用文本文件作为构建上下文时,构建器会将文件解释为Dockerfile。使用文本文件作为上下文意味着构建没有文件系统上下文。

当您的Dockerfile不依赖于任何本地文件时,您可以使用空的构建上下文进行构建。

如何在没有上下文的情况下构建

您可以通过标准输入流传递文本文件,或者指向远程文本文件的URL。


$ docker build - < Dockerfile
Get-Content Dockerfile | docker build -
docker build -t myimage:latest - <<EOF
FROM busybox
RUN echo "hello world"
EOF
$ docker build https://raw.githubusercontent.com/dvdksn/clockbox/main/Dockerfile

当你在没有文件系统上下文的情况下构建时,Dockerfile 指令如 COPY 不能引用本地文件:

$ ls
main.c
$ docker build -<<< $'FROM scratch\nCOPY main.c .'
[+] Building 0.0s (4/4) FINISHED
 => [internal] load build definition from Dockerfile       0.0s
 => => transferring dockerfile: 64B                        0.0s
 => [internal] load .dockerignore                          0.0s
 => => transferring context: 2B                            0.0s
 => [internal] load build context                          0.0s
 => => transferring context: 2B                            0.0s
 => ERROR [1/1] COPY main.c .                              0.0s
------
 > [1/1] COPY main.c .:
------
Dockerfile:2
--------------------
   1 |     FROM scratch
   2 | >>> COPY main.c .
   3 |
--------------------
ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref 7ab2bb61-0c28-432e-abf5-a4c3440bc6b6::4lgfpdf54n5uqxnv9v6ymg7ih: "/main.c": not found

.dockerignore 文件

你可以使用.dockerignore文件来排除构建上下文中的文件或目录。

# .dockerignore
node_modules
bar

这有助于避免将不需要的文件和目录发送到构建器,从而提高构建速度,尤其是在使用远程构建器时。

文件名和位置

当你运行构建命令时,构建客户端会在上下文的根目录中查找名为 .dockerignore 的文件。如果该文件存在,匹配文件中模式的文件和目录会在发送到构建器之前从构建上下文中移除。

如果您使用多个Dockerfile,您可以为每个Dockerfile使用不同的忽略文件。您可以通过为忽略文件使用特殊的命名约定来实现这一点。将您的忽略文件放在与Dockerfile相同的目录中,并在忽略文件前加上Dockerfile的名称,如下例所示。

.
├── index.ts
├── src/
├── docker
│   ├── build.Dockerfile
│   ├── build.Dockerfile.dockerignore
│   ├── lint.Dockerfile
│   ├── lint.Dockerfile.dockerignore
│   ├── test.Dockerfile
│   └── test.Dockerfile.dockerignore
├── package.json
└── package-lock.json

如果两者都存在,Dockerfile特定的忽略文件优先于构建上下文根目录下的.dockerignore文件。

语法

.dockerignore 文件是一个以换行符分隔的模式列表,类似于 Unix shell 的文件通配符。忽略模式中的前导和尾随斜杠将被忽略。以下模式都排除了构建上下文根目录下子目录 foo 中名为 bar 的文件或目录:

  • /foo/bar/
  • /foo/bar
  • foo/bar/
  • foo/bar

如果.dockerignore文件中的一行以#开头,那么这一行被视为注释,并在CLI解释之前被忽略。

#/this/is/a/comment

如果您有兴趣了解.dockerignore模式匹配逻辑的精确细节,请查看GitHub上的moby/patternmatcher仓库,其中包含源代码。

匹配

以下代码片段展示了一个示例 .dockerignore 文件。

# comment
*/temp*
*/*/temp*
temp?

此文件导致以下构建行为:

RuleBehavior
# commentIgnored.
*/temp*Exclude files and directories whose names start with temp in any immediate subdirectory of the root. For example, the plain file /somedir/temporary.txt is excluded, as is the directory /somedir/temp.
*/*/temp*Exclude files and directories starting with temp from any subdirectory that is two levels below the root. For example, /somedir/subdir/temporary.txt is excluded.
temp?Exclude files and directories in the root directory whose names are a one-character extension of temp. For example, /tempa and /tempb are excluded.

匹配是使用Go的 filepath.Match 函数 规则进行的。 预处理步骤使用Go的 filepath.Clean 函数 来修剪空白并移除 ...。 预处理后为空的行的将被忽略。

注意

由于历史原因,模式 . 被忽略。

除了Go的filepath.Match规则外,Docker还支持一个特殊的通配符字符串**,它可以匹配任意数量的目录(包括零个)。例如,**/*.go会排除构建上下文中任何位置找到的以.go结尾的所有文件。

你可以使用.dockerignore文件来排除Dockerfile.dockerignore文件。这些文件仍然会被发送到构建器,因为它们是运行构建所需的。但是你不能使用ADDCOPY或绑定挂载将这些文件复制到镜像中。

否定匹配

你可以在行前加上!(感叹号)来对排除项进行例外处理。以下是一个使用这种机制的.dockerignore文件示例:

*.md
!README.md

上下文目录下的所有markdown文件除了 README.md 都被排除在上下文之外。请注意,子目录下的markdown文件仍然包含在内。

! 异常规则的放置位置会影响行为:匹配特定文件的 .dockerignore 的最后一行决定了它是被包含还是被排除。考虑以下示例:

*.md
!README*.md
README-secret.md

除了README-secret.md之外,上下文中没有包含其他Markdown文件。

现在考虑这个例子:

*.md
README-secret.md
!README*.md

所有的README文件都已包含。中间的行没有效果,因为 !README*.md 匹配 README-secret.md 并且排在最后。