构建上下文
docker build
和 docker buildx build
命令根据 Dockerfile 和上下文构建 Docker 镜像。
什么是构建上下文?
构建上下文是构建可以访问的一组文件。传递给构建命令的位置参数指定要用于构建的上下文。
$ docker build [OPTIONS] PATH | URL | -
^^^^^^^^^^^^^^
您可以将以下任何输入作为构建的上下文:
- 本地目录的相对或绝对路径
- Git 仓库、压缩包或纯文本文件的远程 URL
- 通过标准输入管道传输到
docker build
命令的纯文本文件或压缩包
文件系统上下文
当您的构建上下文是本地目录、远程 Git 仓库或 tar 文件时,这将成为构建器在构建过程中可以访问的文件集。COPY
和 ADD
等构建指令可以引用上下文中的任何文件和目录。
文件系统构建上下文会递归处理。
- 当您指定本地目录或压缩包时,将包含所有子目录。
- 当您指定远程 Git 仓库时,将包含仓库和所有子模块。
有关您可以与构建一起使用的不同类型的文件系统上下文的更多信息,请参阅
文本文件上下文
当您的构建上下文是纯文本文件时,构建器会将该文件解释为 Dockerfile。使用这种方法,构建不会使用文件系统上下文。
更多信息,请参阅 空构建上下文。
本地上下文
要使用本地构建上下文,您可以将相对或绝对文件路径指定给 docker build
命令。以下示例显示一个使用当前目录 (.
) 作为构建上下文的构建命令:
$ docker build .
...
#16 [internal] load build context
#16 sha256:23ca2f94460dcbaf5b3c3edbaaa933281a4e0ea3d92fe295193e4df44dc68f85
#16 transferring context: 13.16MB 2.2s done
...
这使得当前工作目录中的文件和目录可供构建器使用。构建器会在需要时从构建上下文加载它所需的文件。
您还可以使用本地压缩包作为构建上下文,方法是将压缩包内容管道传输到 docker build
命令。请参阅 压缩包。
本地目录
考虑以下目录结构:
.
├── 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
本地压缩包
当您将压缩包管道传输到构建命令时,构建会使用压缩包的内容作为文件系统上下文。
例如,给定以下项目目录:
.
├── Dockerfile
├── Makefile
├── README.md
├── main.c
├── scripts
├── src
└── test.Dockerfile
您可以创建目录的压缩包并将其管道传输到构建以用作上下文:
$ tar czf foo.tar.gz *
$ docker build - < foo.tar.gz
构建会从压缩包上下文中解析 Dockerfile。您可以使用 --file
标志指定相对于压缩包根目录的 Dockerfile 的名称和位置。以下命令使用压缩包中的 test.Dockerfile
进行构建:
$ docker build --file test.Dockerfile - < foo.tar.gz
远程上下文
您可以指定远程 Git 仓库、压缩包或纯文本文件的地址作为您的构建上下文。
如果远程压缩包是文本文件,则构建器将不会接收 文件系统上下文,而是假设远程文件是 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
下表列出了所有有效的后缀及其构建上下文
构建语法后缀 | 使用的提交 | 使用的构建上下文 |
---|---|---|
myrepo.git | refs/heads/<默认分支> | / |
myrepo.git#mytag | refs/tags/mytag | / |
myrepo.git#mybranch | refs/heads/mybranch | / |
myrepo.git#pull/42/head | refs/pull/42/head | / |
myrepo.git#:myfolder | refs/heads/<默认分支> | /myfolder |
myrepo.git#master:myfolder | refs/heads/master | /myfolder |
myrepo.git#mytag:myfolder | refs/tags/mytag | /myfolder |
myrepo.git#mybranch:myfolder | refs/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-worldGitHub 存储库的hello.c
文件。
docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c ./
EOF
远程压缩包
如果您传递到远程tarball的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 存档,并且可以使用xz
、bzip2
、gzip
或identity
(无压缩)中的任何一种格式进行压缩。
空上下文
当您使用文本文件作为构建上下文时,构建器会将该文件解释为 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
在没有文件系统上下文的情况下构建时,COPY
之类的 Dockerfile 指令无法引用本地文件。
$ 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
文件中的某一行以第 1 列的#
开头,则该行将被视为注释,并在 CLI 解释之前被忽略。
#/this/is/a/comment
如果您有兴趣了解.dockerignore
模式匹配逻辑的精确细节,请查看GitHub 上的 moby/patternmatcher 存储库,其中包含源代码。
匹配
以下代码片段显示了一个.dockerignore
文件示例。
# comment
*/temp*
*/*/temp*
temp?
此文件导致以下构建行为
规则 | 行为 |
---|---|
# 注释 | 被忽略。 |
*/temp* | 排除任何根目录的直接子目录中名称以temp 开头的文件和目录。例如,普通文件/somedir/temporary.txt 被排除,目录/somedir/temp 也被排除。 |
*/*/temp* | 从根目录下两级子目录中排除以temp 开头的文件和目录。例如,/somedir/subdir/temporary.txt 被排除。 |
temp? | 排除根目录中名称为temp 后跟一个字符的文件和目录。例如,/tempa 和/tempb 被排除。 |
匹配使用 Go 的filepath.Match
函数规则。预处理步骤使用 Go 的filepath.Clean
函数规则。预处理步骤使用 Go 的filepath.Clean
函数来修剪空格并删除.
和..
。预处理后为空白行的行将被忽略。
注意
出于历史原因,模式
.
被忽略。
除了 Go 的filepath.Match
规则外,Docker 还支持特殊的通配符字符串**
,它匹配任意数量的目录(包括零个)。例如,**/*.go
会排除在构建上下文中找到的所有以.go
结尾的文件。
您可以使用.dockerignore
文件排除Dockerfile
和.dockerignore
文件。这些文件仍然会发送到构建器,因为它们是运行构建所必需的。但是,您不能使用ADD
、COPY
或绑定挂载将这些文件复制到镜像中。
否定匹配
您可以使用!
(感叹号)作为行的前缀,以对排除项进行例外处理。以下是使用此机制的.dockerignore
文件示例
*.md
!README.md
上下文目录下的所有 markdown 文件(README.md
除外)都从上下文中排除。请注意,子目录下的 markdown 文件仍然包含在内。
!
例外规则的放置会影响行为:.dockerignore
中匹配特定文件的最后一行决定了它是否包含或排除。考虑以下示例
*.md
!README*.md
README-secret.md
除README-secret.md
外,上下文中不包含任何README文件以外的Markdown文件。
现在考虑这个例子
*.md
README-secret.md
!README*.md
所有README文件都包含在内。中间一行无效,因为!README*.md
匹配README-secret.md
并且位于最后。