[TOC]
# [Dockerfile参考](https://docs.docker.com/engine/reference/builder)
Docker可以通过阅读Docker中的指令来自动构建映像`Dockerfile`。A`Dockerfile`是一个文本文档,其中包含用户可以在命令行上调用以组装图像的所有命令。使用`docker build`用户可以创建自动构建,该构建连续执行多个命令行指令。
本页描述您可以在中使用的命令`Dockerfile`。阅读完此页面后,请参考[`Dockerfile`最佳实践](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/)以获取有关技巧的指南。
## Usage
[docker build](https://docs.docker.com/engine/reference/commandline/build/)命令从一个构建的图像`Dockerfile`和一个*上下文*。构建的上下文是指定位置`PATH`或的文件集`URL`。这`PATH`是本地文件系统上的目录。该`URL`是一个Git仓库的位置。
上下文是递归处理的。因此,a`PATH`包括任何子目录,并且a包括`URL`存储库及其子模块。此示例显示了一个使用当前目录作为上下文的构建命令:
~~~
$ docker build .
Sending build context to Docker daemon 6.51 MB
...
~~~
构建是由Docker守护程序而不是CLI运行的。构建过程要做的第一件事是将整个上下文(递归)发送到守护程序。在大多数情况下,最好从一个空目录作为上下文开始,并将您的Dockerfile保留在该目录中。仅添加构建Dockerfile所需的文件。
> **警告**
>
> 不要用你的根目录下,`/`作为`PATH`因为它会导致生成到您的硬盘驱动器的全部内容传输到docker护进程。
要在构建上下文中使用文件,`Dockerfile`引用指的是指令(例如,`COPY`指令)中指定的文件。要提高构建的性能,请通过将`.dockerignore`文件添加到上下文目录来排除文件和目录。有关如何[创建`.dockerignore`文件的信息,](https://docs.docker.com/engine/reference/builder/#dockerignore-file)请参阅此页面上的文档。
传统上,`Dockerfile`称为,`Dockerfile`并且位于上下文的根中。您可以使用`-f`标志with`docker build`指向文件系统中任何位置的Dockerfile。
~~~
$ docker build -f /path/to/a/Dockerfile .
~~~
如果构建成功,则可以指定存储新映像的存储库和标记:
~~~
$ docker build -t shykes/myapp .
~~~
要在构建后将映像标记到多个存储库中,请在`-t`运行`build`命令时添加多个参数:
~~~
$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
~~~
在Docker守护程序运行中的指令之前`Dockerfile`,它会对进行初步验证,`Dockerfile`如果语法不正确,则会返回错误:
~~~
$ docker build -t test/myapp .
Sending build context to Docker daemon 2.048 kB
Error response from daemon: Unknown instruction: RUNCMD
~~~
Docker守护程序以`Dockerfile`一对一的方式运行指令,如有必要,将每个指令的结果提交到新映像,然后最终输出新映像的ID。Docker守护程序将自动清理您发送的上下文。
请注意,每条指令都是独立运行的,并会导致创建新的映像-因此`RUN cd /tmp`对下一条指令不会有任何影响。
Docker将尽可能重用中间映像(缓存),以`docker build`显着加速该过程。这由`Using cache`控制台输出中的消息指示。(有关详细信息,请参阅[`Dockerfile`最佳做法指南](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/):
~~~
$ docker build -t svendowideit/ambassador .
Sending build context to Docker daemon 15.36 kB
Step 1/4 : FROM alpine:3.2
---> 31f630c65071
Step 2/4 : MAINTAINER SvenDowideit@home.org.au
---> Using cache
---> 2a1c91448f5f
Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/
---> Using cache
---> 21ed6e7fbb73
Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
---> Using cache
---> 7ea8aef582cc
Successfully built 7ea8aef582cc
~~~
构建缓存仅用于具有本地父链的图像。这意味着这些图像是由以前的版本创建的,或者整个图像链都已加载`docker load`。如果您希望使用特定映像的构建缓存,则可以使用`--cache-from`选项指定它。指定带有的图像`--cache-from`不需要具有父链,并且可以从其他注册表中提取。
完成构建后,您就可以研究[*将存储库推送到其注册表了*](https://docs.docker.com/engine/tutorials/dockerrepos/#/contributing-to-docker-hub)。
## BuildKit
从版本18.09开始,Docker支持由[moby / buildkit](https://github.com/moby/buildkit)项目提供的用于执行构建的新后端。与旧的实现相比,BuildKit后端提供了许多好处。例如,BuildKit可以:
* 检测并跳过执行未使用的构建阶段
* 并行构建独立的构建阶段
* 两次构建之间仅增量传输构建上下文中的已更改文件
* 在构建上下文中检测并跳过传输未使用的文件
* 使用具有许多新功能的外部Dockerfile实现
* 避免其他API(中间图像和容器)的副作用
* 优先考虑构建缓存以进行自动修剪
要使用BuildKit后端,您需要`DOCKER_BUILDKIT=1`在CLI上设置环境变量,然后再调用`docker build`。
要了解基于BuildKit的构建可用的实验性Dockerfile语法,[请参阅BuildKit存储库中的文档](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md)。
## Format
这是`Dockerfile`的格式:
~~~
# Comment
INSTRUCTION arguments
~~~
该指令不区分大小写。但是,惯例是将它们大写以更轻松地将它们与参数区分开。
Docker`Dockerfile`按顺序运行指令。一个`Dockerfile`**必须以开始`FROM`的指令**。这可能在[解析器指令](https://docs.docker.com/engine/reference/builder/#parser-directives),[注释](https://docs.docker.com/engine/reference/builder/#format)和全局范围的[ARG之后](https://docs.docker.com/engine/reference/builder/#arg)。该`FROM`指令指定要从中构建[*父图像*](https://docs.docker.com/glossary/#parent_image)。`FROM`只能在一个或多个`ARG`指令之前,这些指令声明在中的`FROM`行中使用的参数`Dockerfile`。
该码头工人对待线*开始*以`#`作为注释,除非该行是一个有效的[解析器指令](https://docs.docker.com/engine/reference/builder/#parser-directives)。`#`一行中其他任何地方的标记都被视为参数。这允许如下语句:
~~~
# Comment
RUN echo 'we are running some # of cool things'
~~~
在执行Dockerfile指令之前删除注释行,这意味着以下示例中的注释不会由执行`echo`命令的Shell处理,并且以下两个示例是等效的:
~~~
RUN echo hello \
# comment
world
~~~
~~~
RUN echo hello \
world
~~~
注释中不支持换行符。
> **注意空白**
>
> 为了向后兼容,将忽略但不鼓励在注释(`#`)和指令(例如`RUN`)之前的前导空白。在这些情况下,不保留前导空格,因此以下示例是等效的:
>
> ~~~
> # this is a comment-line
> RUN echo hello
> RUN echo world
>
> ~~~
>
> ~~~
> # this is a comment-line
> RUN echo hello
> RUN echo world
>
> ~~~
>
> 但是请注意,指令*参数*中的空白(例如跟随其后的命令`RUN`)被保留,因此以下示例使用指定的前导空白打印“ hello world”:
>
> ~~~
> RUN echo "\
> hello\
> world"
>
> ~~~
## Parser directives
解析器指令是可选的,并且会影响`Dockerfile`处理a中后续行的方式。解析器指令不会在构建中添加图层,也不会显示为构建步骤。解析器指令以形式写为特殊类型的注释`# directive=value`。单个指令只能使用一次。
处理完注释,空行或生成器指令后,Docker不再寻找解析器指令。而是将格式化为解析器指令的任何内容都视为注释,并且不会尝试验证它是否可能是解析器指令。因此,所有解析器指令必须位于的最顶部`Dockerfile`。
解析器指令不区分大小写。但是,约定是小写的。约定还应在任何解析器指令之后包含一个空白行。解析器伪指令不支持行继续字符。
由于这些规则,以下示例均无效:
由于行继续而无效:
~~~
# direc \
tive=value
~~~
由于出现两次而无效:
~~~
# directive=value1
# directive=value2
FROM ImageName
~~~
由于在生成器指令之后出现,因此被视为注释:
~~~
FROM ImageName
# directive=value
~~~
由于出现在不是解析器指令的注释之后,因此被视为注释:
~~~
# About my dockerfile
# directive=value
FROM ImageName
~~~
由于未被识别,未知指令被视为注释。另外,由于在不是解析器指令的注释之后出现,所以已知指令被视为注释。
~~~
# unknowndirective=value
# knowndirective=value
~~~
解析器指令中允许非换行空格。因此,以下各行都被相同地对待:
~~~
#directive=value
# directive =value
# directive= value
# directive = value
# dIrEcTiVe=value
~~~
支持以下解析器指令:
* `syntax`
* `escape`
## syntax
~~~
# syntax=[remote image reference]
~~~
例如:
~~~
# syntax=docker/dockerfile
# syntax=docker/dockerfile:1.0
# syntax=docker.io/docker/dockerfile:1
# syntax=docker/dockerfile:1.0.0-experimental
# syntax=example.com/user/repo:tag@sha256:abcdef...
~~~
只有在[BuildKit公司](https://docs.docker.com/engine/reference/builder/#buildkit)使用后端
syntax指令定义用于生成当前Dockerfile的Dockerfile生成器的位置。BuildKit后端允许无缝地使用作为Docker映像分发并在容器沙盒环境中执行的构建器的外部实现。
自定义Dockerfile实现允许您:
* 无需更新守护进程即可自动修复错误
* 确保所有用户使用相同的实现来构建Dockerfile
* 使用最新功能而不更新守护程序
* 尝试新的实验或第三方功能
### Official releases
Docker分发可用于在下构建Dockerfiles的映像的官方版本`docker/dockerfile`Docker Hub上的存储库。有两个通道可以发布新的图像:稳定的和实验的。
稳定通道遵循语义版本控制。例如:
* `docker/dockerfile:1.0.0`\-只允许不可变版本 `1.0.0`
* `docker/dockerfile:1.0`\-允许版本 `1.0.*`
* `docker/dockerfile:1`\-允许版本 `1.*.*`
* `docker/dockerfile:latest`\-稳定频道的最新版本
实验性的通道在发布时使用稳定通道中的主要和次要组件的增量版本控制。例如:
* `docker/dockerfile:1.0.1-experimental`\-只允许不可变版本`1.0.1-实验`
* `docker/dockerfile:1.0-experimental`\-之后的最新实验版本 `one`
* `docker/dockerfile:experimental`\-实验频道最新版本
你应该选择最适合你需要的频道。如果您只想修复错误,您应该使用`docker/dockerfile:1.0`. 如果您想从实验特性中获益,您应该使用实验频道。如果您使用的是实验性的通道,较新的版本可能无法向后兼容,因此建议使用不可变的完整版本变体。
有关主版本和夜间功能版本,请参阅中的说明[源存储库](https://github.com/moby/buildkit/blob/master/README.md)
## escape
~~~
# escape=\ (backslash)
~~~
Or
~~~
# escape=` (backtick)
~~~
这个`escape`指令设置用于转义 `Dockerfile文件`. 如果未指定,则默认转义字符为`\`.
转义符既用于转义行中的字符,也用于转义换行符。这允许`Dockerfile`跨越多行的指令。请注意,无论`逃跑`解析器指令包含在`Dockerfile`,*在中不执行转义`RUN`命令,除了在行尾*
将转义符设置为`is especially useful on`Windows`, where`\`is the directory path separator.`与[Windows PowerShell](https://technet.microsoft.com/en-us/library/hh847755.aspx).
考虑下面的例子,它会以不明显的方式失败`Windows`. 第二次 `\`在第二行末尾,将被解释为新行的转义,而不是从第一行转义的目标`\`. 同样 `\`在第三行的末尾,假设它实际上是作为一条指令来处理的,会导致它被视为行的延续。此dockerfile的结果是第二行和第三行被视为一条指令:
~~~
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
~~~
结果:
~~~
PS C:\John> docker build -t cmd .
Sending build context to Docker daemon 3.072 kB
Step 1/2 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/2 : COPY testfile.txt c:\RUN dir c:
GetFileAttributesEx c:RUN: The system cannot find the file specified.
PS C:\John>
~~~
解决上述问题的一种方法是使用`/`作为两个目标`复制`说明,以及`dir`. 然而,这种语法充其量是令人困惑的,因为它对于上的路径来说是不自然的`窗户`,最坏的情况是,由于不是所有命令都打开,所以容易出错`Windows`支持 `/`作为路径分隔符
通过添加`escape`解析器指令,如下所示 `Dockerfile文件`对上的文件路径使用自然平台语义,如预期的那样成功`Windows`:
~~~
# escape=`
FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\
~~~
结果:
~~~
PS C:\John> docker build -t succeeds --no-cache=true .
Sending build context to Docker daemon 3.072 kB
Step 1/3 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/3 : COPY testfile.txt c:\
---> 96655de338de
Removing intermediate container 4db9acbb1682
Step 3/3 : RUN dir c:\
---> Running in a2c157f842f5
Volume in drive C has no label.
Volume Serial Number is 7E6D-E0F7
Directory of c:\
10/05/2016 05:04 PM 1,894 License.txt
10/05/2016 02:22 PM <DIR> Program Files
10/05/2016 02:14 PM <DIR> Program Files (x86)
10/28/2016 11:18 AM 62 testfile.txt
10/28/2016 11:20 AM <DIR> Users
10/28/2016 11:20 AM <DIR> Windows
2 File(s) 1,956 bytes
4 Dir(s) 21,259,096,064 bytes free
---> 01c7f3bef04f
Removing intermediate container a2c157f842f5
Successfully built 01c7f3bef04f
PS C:\John>
~~~
## Environment replacement
环境变量(用[这个`ENV`陈述](https://docs.docker.com/engine/reference/builder/#env))也可以在某些指令中用作要由 `Dockerfile文件`. 还可以处理转义,以便将类似变量的语法逐字包含到语句中。
环境变量在`Dockerfile`或者`$variable_名称`或`${variable_name}`. 它们被同等对待,大括号语法通常用于解决变量名没有空格的问题,例如`${foo}\u条`.
这个`${variable_name}`语法也支持一些标准`猛击`以下指定的修饰符:
* `${variable:-word}`表示如果`变量`则结果将为该值。如果`variable`当时还没有设定`单词`结果就是这样
* `${variable:+word}`表示如果`变量`就这样定下来了`word`,否则结果为空字符串。
在所有情况下,`word`可以是任何字符串,包括其他环境变量。
通过添加一个`\`变量前:`\$福`或`\${foo}`例如,将转换为`$福`和`${foo}`文字
示例(解析后的表示将显示在`#`):
~~~
FROM busybox
ENV FOO=/bar
WORKDIR ${FOO} # WORKDIR /bar
ADD . $FOO # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux
~~~
中的以下指令列表支持环境变量`Dockerfile`:
* `ADD`
* `COPY`
* `ENV`
* `EXPOSE`
* `FROM`
* `LABEL`
* `STOPSIGNAL`
* `USER`
* `VOLUME`
* `WORKDIR`
* `ONBUILD`(与上述支持的指令之一结合使用时)
环境变量替换将在整个指令中为每个变量使用相同的值。换句话说,在这个例子中:
~~~
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
~~~
会导致`def`有价值的`hello`,不是`bye`. 然而, `ghi`将具有值`bye`因为它不是同一指令的一部分`abc`到`bye`.
##
## .dockerignore file
在docker CLI将上下文发送到docker守护程序之前,它将查找名为`.dockerignore`在上下文的根目录中。如果此文件存在,CLI将修改上下文以排除与其中模式匹配的文件和目录。这有助于避免不必要地向守护进程发送大的或敏感的文件和目录,并可能使用将它们添加到映像中`添加`或`COPY`.
CLI解释`.dockerignore`文件作为一个换行分隔的模式列表,类似于unixshell的fileglobs。为了进行匹配,上下文的根目录被认为是工作目录和根目录。例如,模式`/食品/酒吧`和`foo/bar`两者都排除名为`酒吧`在`foo`的子目录`路径`或位于git存储库的根目录中`URL`. 也不排除任何其他因素。
如果有人排队`.dockerignore`文件以开头 `#`在第1列中,这一行被视为注释,在被CLI解释之前被忽略。
这里有一个例子`.dockerignore`文件:
~~~gitignore
# comment
*/temp*
*/*/temp*
temp?
~~~
此文件导致以下生成行为:
| 规则 | 行为 |
| --- | --- |
| `# comment` | 忽略 |
| `*/temp*` | 排除名称以开头的文件和目录`temp`在根的任何直接子目录中。例如,普通文件`/somedir/temporary.txt`被排除,目录也是`/somedir/temp`. |
| `*/*/temp*` | 排除以开头的文件和目录`temp`从根目录下两级的任何子目录。例如,`/somedir/subdir/temporary.txt`被排除在外 |
| `temp?` | 排除根目录中名称扩展名为一个字符的文件和目录`temp`. 例如, `我是你吗/`和`/tempb`被排除在外 |
匹配是用Go完成的[文件路径匹配](http://golang.org/pkg/path/filepath#Match)规则。预处理步骤删除前导空格和尾随空格并消除`.`和 `..`使用Go的元素[文件路径。清除](http://golang.org/pkg/path/filepath/#Clean). 预处理后空白的行将被忽略。
除了Go的filepath.Match规则之外,Docker还支持一个特殊的通配符字符串`**`匹配任意数量的目录(包括零)。例如,`**/*走吧`将排除以结尾的所有文件`.go`可以在所有目录中找到,包括生成上下文的根目录。
以开头的行`!`(感叹号)可用于排除例外。下面是一个例子 `.dockerignore公司`使用此机制的文件:
~~~gitignore
*.md
!README.md
~~~
所有markdown文件*除了*`README.md`从上下文中排除
安置`!`异常规则影响行为:最后一行 `.dockerignore`与特定文件匹配的值将决定是否包含或排除该文件。考虑以下示例:
~~~gitignore
*.md
!README*.md
README-secret.md
~~~
上下文中不包括除自述文件以外的任何降价文件`README-secret.md`.
现在考虑这个例子:
~~~gitignore
*.md
README-secret.md
!README*.md
~~~
包括所有自述文件。中间线没有效果,因为`!README*.md`比赛`readme-secret.md`最后一名
你甚至可以使用`.dockerignore`要排除的文件 `Dockerfile文件`和`.dockerignore`文件夹。这些文件仍然被发送到守护进程,因为它需要它们来完成它的工作。但是`添加`和`COPY`说明不会将它们复制到映像中。
最后,您可能希望指定在上下文中包括哪些文件,而不是排除哪些文件。为此,请指定`*`作为第一个模式,后面跟着一个或多个 `!`异常模式
> **注意**
>
>由于历史原因`.`被忽略
## FROM
~~~
FROM [--platform=<platform>] <image> [AS <name>]
~~~
Or
~~~
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
~~~
Or
~~~
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
~~~
这个`FROM`指令初始化新的生成阶段并设置[*基本图像*](https://docs.docker.com/glossary/#base_image)有关后续说明。因此,一个有效的`Dockerfile`必须以`从`说明。图像可以是任何有效的图像,尤其是从**绘制图像**从[*公共存储库*](https://docs.docker.com/docker-hub/repos/).
* `ARG`是唯一可以放在前面的指令`从`在`Dockerfile`. 看到了吗[了解ARG和FROM如何交互](https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact).
* `FROM`可以在一个 `Dockerfile文件`创建多个映像或将一个生成阶段用作另一个生成阶段的依赖项。只需在每次更新之前记录提交输出的最后一个图像ID`FROM`说明。每个`从`指令清除先前指令创建的任何状态。
* 可选地,可以通过添加`AS name`致`从`说明。该名称可用于后续`FROM`和`COPY --from=<name>`说明引用在此阶段构建的映像。
* 这个`tag`或`消化`值是可选的。如果忽略其中任何一个,则构建器假定`latest`默认情况下标记。如果找不到`标签`价值观
可选的`--platform`标志可用于指定图像的平台,以防`从`引用多平台映像。例如,`linux/amd64`,`linux/arm64`,或`windows/amd64`. 默认情况下,使用生成请求的目标平台。例如,可以在该标志的值中使用全局生成参数[自动平台参数](https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope)允许您强制阶段到本机构建平台(`--platform=$BUILDPLATFORM`),并在阶段内使用它交叉编译到目标平台。
### Understand how ARG and FROM interact
`FROM`指令支持由`ARG`出现在第一个指令之前的指令`FROM`.
~~~
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
~~~
一个`ARG`在a之前声明`FROM`在生成阶段之外,因此不能在`FROM`. 使用的默认值`ARG`在第一个`FROM`使用`ARG`生成阶段内没有值的指令:
~~~
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
~~~
## RUN
RUN有两种形式:
* `RUN <command>`(*壳*窗体中,命令在shell中运行,默认情况下是`/bin/sh -c`在Linux或 `命令/序号`在Windows上)
* `RUN ["executable", "param1", "param2"]`(*执行官*形式)
这个`RUN`指令将在当前图像顶部的新层中执行任何命令并提交结果。生成的提交映像将用于 `Dockerfile文件`.
分层`RUN`指令和生成提交符合Docker的核心概念,在Docker中提交很便宜,并且可以从映像历史的任何一点创建容器,就像源代码管理一样。
这个*执行官*形式可以避免贝壳串咀嚼,并`RUN`使用不包含指定的shell可执行文件的基映像的命令。
的默认shell*壳*可以使用`SHELL`命令
在*壳*您可以使用`\`(反斜杠)将单个运行指令继续到下一行。例如,考虑以下两行
~~~
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
~~~
它们一起相当于这一行:
~~~
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
~~~
要使用除‘/bin/sh‘以外的其他shell,请使用*执行官*通过所需外壳的形状。例如:
~~~
RUN ["/bin/bash", "-c", "echo hello"]
~~~
> **注意**
>
> 这个*执行官*表单被解析为JSON数组,这意味着您必须在单词周围使用双引号(“),而不是单引号(')。
不像*壳*形式上*执行官*窗体不调用命令shell。这意味着正常的shell处理不会发生。例如,`RUN [ "echo", "$HOME" ]`不会对执行变量替换`$家`. 如果需要shell处理,则使用*壳*直接形成或执行shell,例如:`RUN [ "sh", "-c", "echo $HOME" ]`. 当使用exec form并直接执行shell时(如shell form的情况),执行环境变量扩展的是shell,而不是docker。
> **注意**
>
> 在*JSON格式*窗体中,必须避免反斜杠。这在反斜杠是路径分隔符的Windows上特别相关。否则,以下行将被视为*壳*由于不是有效的JSON,并以意外方式失败:
>
> ~~~
> RUN ["c:\windows\system32\tasklist.exe"]
>
> ~~~
>
> 此示例的正确语法是:
>
> ~~~
> RUN ["c:\\windows\\system32\\tasklist.exe"]
>
> ~~~
缓存`RUN`指令不会在下一个构建期间自动失效。像这样的指令的缓存`RUN apt get dist upgrade-y`将在下一个生成期间重用。的缓存`RUN`指令可以通过使用`--无缓存`比如说国旗`docker build --no-cache`.
见[`Dockerfile`最佳实践指南](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/)了解更多信息
的缓存`RUN`指令可以通过以下方式失效[`ADD`](https://docs.docker.com/engine/reference/builder/#add)和[`COPY`](https://docs.docker.com/engine/reference/builder/#copy)说明的缓存`RUN`指令不会在下一个构建期间自动失效。像这样的指令的缓存`运行apt get dist upgrade-y`将在下一个生成期间重用。的缓存`RUN`指令可以通过使用`--无缓存`比如说国旗`docker build --no-cache`.
见[`Dockerfile`最佳实践指南](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/)了解更多信息
的缓存`RUN`指令可以通过以下方式失效[`ADD`](https://docs.docker.com/engine/reference/builder/#add)和[`COPY`](https://docs.docker.com/engine/reference/builder/#copy)说明
### Known issues (RUN)
* * [第783期](https://github.com/docker/docker/issues/783)是关于使用AUFS文件系统时可能出现的文件权限问题。你可能会注意到`rm`例如一个文件
对于具有最新aufs版本的系统(即。,`dirperm1`可通过挂载选项自动设置挂载问题) `直管1`选项。更多详细信息`dirperm1`选项可在[`aufs`手册页](https://github.com/sfjro/aufs3-linux/tree/aufs3.18/Documentation/filesystems/aufs)
如果您的系统不支持`dirperm1`,该问题描述了一种解决方法。
## CMD
这个`CMD`指令有三种形式:
* `CMD ["executable","param1","param2"]`(*执行官*表格,这是首选表格)
* `CMD ["param1","param2"]`(作为*入口点的默认参数*)
* `CMD command param1 param2`(*壳*形式)
只能有一个`CMD`a中的指令 `Dockerfile文件`. 如果你列出不止一个`CMD`那就只剩下最后一个了 `命令`将生效
\*\*a的主要目的`CMD`为正在执行的容器提供默认值。\*\*这些默认值可以包括可执行文件,也可以省略可执行文件,在这种情况下,必须指定`入口点`还有指导
如果`CMD`用于为`入口点`指令,两个`CMD`和`入口点`指令应使用JSON数组格式指定。
> **注意**
>
> 这个*执行官*表单被解析为JSON数组,这意味着您必须在单词周围使用双引号(“),而不是单引号(')。
不像*壳*形式上*执行官*窗体不调用命令shell。这意味着正常的shell处理不会发生。例如,`CMD [ "echo", "$HOME" ]`不会对执行变量替换`$家`. 如果需要shell处理,则使用*壳*直接形成或执行shell,例如:`CMD [ "sh", "-c", "echo $HOME" ]`. 当使用exec form并直接执行shell时(如shell form的情况),执行环境变量扩展的是shell,而不是docker。
在shell或exec格式中使用时`CMD`指令设置运行映像时要执行的命令。
如果你使用*壳*形式`CMD`,然后`<command>`将在中执行`/bin/sh -c`:
~~~
FROM ubuntu
CMD echo "This is a test." | wc -
~~~
如果你想的话**运行您的**`<command>`**没有外壳**必须将JSON\*数组的格式设置为完整的JSON\*格式,然后将此命令的格式设置为express`CMD`.\*\*任何附加参数都必须在数组中单独表示为字符串:
~~~
FROM ubuntu
CMD ["/usr/bin/wc","--help"]
~~~
如果希望容器每次都运行相同的可执行文件,则应考虑使用`ENTRYPOINT`与 `命令`. 看到了吗[*入口点*](https://docs.docker.com/engine/reference/builder/#entrypoint).
如果用户指定参数`docker run`然后它们将重写中指定的默认值 `命令`.
> **注意**
>
> 不要混淆`RUN`具有 `命令`.`RUN`实际运行命令并提交结果; `命令`不在生成时执行任何操作,但指定映像的预期命令。
## LABEL
~~~
LABEL <key>=<value> <key>=<value> <key>=<value> ...
~~~
这个`LABEL`指令将元数据添加到图像中。A`标签`是一个键值对。在`LABEL`值,请像在命令行分析中那样使用引号和反斜杠。一些用法示例:
~~~
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
~~~
一个图像可以有多个标签。可以在一行上指定多个标签。在Docker1.10之前,这降低了最终图像的大小,但现在已经不是这样了。您仍然可以选择在一条指令中指定多个标签,方法有以下两种:
~~~
LABEL multi.label1="value1" multi.label2="value2" other="value3"
~~~
~~~
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
~~~
基本图像或父图像中包含的标签(中的图像`FROM`(行)由您的图像继承。如果标签已经存在,但具有不同的值,则最近应用的值将覆盖以前设置的任何值。
要查看图像的标签,请使用`docker image inspect`命令。你可以使用`--格式`选择只显示标签;
~~~
docker image inspect --format='' myimage
~~~
~~~
{
"com.example.vendor": "ACME Incorporated",
"com.example.label-with-value": "foo",
"version": "1.0",
"description": "This text illustrates that label-values can span multiple lines.",
"multi.label1": "value1",
"multi.label2": "value2",
"other": "value3"
}
~~~
## MAINTAINER (已弃用)
~~~
MAINTAINER <name>
~~~
这个`MAINTAINER`指令设置*作者*生成图像的字段。这个`LABEL`指令是这个版本的一个更灵活的版本,您应该使用它,因为它可以设置您需要的任何元数据,并且可以很容易地查看,例如使用`码头工人检查`. 设置与`MAINTAINER`您可以使用的字段:
~~~
LABEL maintainer="SvenDowideit@home.org.au"
~~~
然后可以从`docker inspect`还有其他的标签
## EXPOSE
~~~
EXPOSE <port> [<port>/<protocol>...]
~~~
这个`EXPOSE`指令通知Docker容器在运行时侦听指定的网络端口。可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认为TCP。
这个`EXPOSE`指令并不实际发布端口。它的功能是构建映像的人和运行容器的人之间的一种文档类型,关于要发布哪些端口。要在运行容器时实际发布端口,请使用`-p`标志打开`docker run`发布和映射一个或多个端口,或`-P`用于发布所有公开端口并将它们映射到高阶端口的标志。
默认情况下,`EXPOSE`假设为TCP。也可以指定UDP:
~~~
EXPOSE 80/udp
~~~
要同时在TCP和UDP上公开,请包括两行:
~~~
EXPOSE 80/tcp
EXPOSE 80/udp
~~~
在这种情况下,如果您使用`-P`具有`docker run`,对于TCP和UDP,端口将公开一次。记住这一点`-P`在主机上使用短暂的高顺序主机端口,因此对于TCP和UDP,端口将不同。
不管`EXPOSE`设置,可以在运行时使用`-p`旗子。例如
~~~
docker run -p 80:80/tcp -p 80:80/udp ...
~~~
要在主机系统上设置端口重定向,请参阅[使用-P标志](https://docs.docker.com/engine/reference/run/#expose-incoming-ports). 这个`docker network`命令支持创建用于容器之间通信的网络,而无需公开或发布特定端口,因为连接到网络的容器可以通过任何端口彼此通信。有关详细信息,请参见[此功能概述](https://docs.docker.com/engine/userguide/networking/).
## ENV
~~~
ENV <key>=<value> ...
~~~
该`ENV`指令将环境变量`<key>`设置为 value`<value>`。此值将在构建阶段的所有后续指令的环境中,并且也可以在许多中[内联替换](https://docs.docker.com/engine/reference/builder/#environment-replacement)。该值将被解释为其他环境变量,因此如果未转义引号字符将被删除。与命令行解析一样,引号和反斜杠可用于在值中包含空格。
例子:
~~~
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
~~~
该`ENV`指令允许`<key>=<value> ...`一次设置多个变量,下面的示例将在最终图像中产生相同的净结果:
~~~
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
MY_CAT=fluffy
~~~
`ENV`当容器从生成的映像运行时,使用设置的环境变量将持续存在。您可以使用 来查看值`docker inspect`,并使用 更改它们`docker run --env <key>=<value>`。
环境变量持久性可能会导致意外的副作用。例如,设置会`ENV DEBIAN_FRONTEND=noninteractive`更改 的行为`apt-get`,并可能使用户对您的图像感到困惑。
如果仅在构建期间需要环境变量,而不是在最终映像中,请考虑为单个命令设置一个值:
~~~
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ...
~~~
或者使用[`ARG`](https://docs.docker.com/engine/reference/builder/#arg),它不会保留在最终图像中:
~~~
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y ...
~~~
> **替代语法**
>
> 该`ENV`指令还允许使用替代语法`ENV <key> <value>`,省略`=`.例如:
>
> ~~~
> ENV MY_VAR my-value
>
> ~~~
>
> 此语法不允许在单个`ENV`指令中设置多个环境变量,并且可能会造成混淆。例如,以下内容设置了一个(`ONE`)带有 value 的环境变量 `"TWO= THREE=world"`:
>
> ~~~
> ENV ONE TWO= THREE=world
>
> ~~~
>
> 支持替代语法以实现向后兼容性,但由于上述原因不鼓励使用,并且可能会在未来版本中删除。
## ADD
ADD有两种形式:
~~~
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
~~~
包含空格的路径需要后一种形式。
> **笔记**
>
> 该`--chown`功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器。由于用户和组所有权概念不Linux和Windows,使用之间进行转换`/etc/passwd`,并`/etc/group`用于转换的用户和组名ID的限制此功能仅适用于基于Linux操作系统的容器是可行的。
该`ADD`指令从中复制新文件、目录或远程文件 URL`<src>`,并将它们添加到路径 处的图像文件系统中`<dest>`。
`<src>`可以指定多个资源,但如果它们是文件或目录,则它们的路径被解释为相对于构建上下文的源。
每个都`<src>`可能包含通配符,匹配将使用 Go 的[filepath.Match](http://golang.org/pkg/path/filepath#Match)规则完成。例如:
添加所有以“hom”开头的文件:
~~~
ADD hom* /mydir/
~~~
在下面的示例中,`?`被替换为任何单个字符,例如“home.txt”。
~~~
ADD hom?.txt /mydir/
~~~
`<dest>`是一个绝对路径,或相对于一个路径`WORKDIR`,到其中的源将在目标容器内进行复制。
下面的示例使用相对路径,并将“test.txt”添加到`<WORKDIR>/relativeDir/`:
~~~
ADD test.txt relativeDir/
~~~
而此示例使用绝对路径,并将“test.txt”添加到`/absoluteDir/`
~~~
ADD test.txt /absoluteDir/
~~~
添加包含特殊字符(如`[`和`]`)的文件或目录时,您需要按照 Golang 规则对这些路径进行转义,以防止它们被视为匹配模式。例如,要添加名为 的文件`arr[0].txt`,请使用以下命令;
~~~
ADD arr[[]0].txt /mydir/
~~~
所有新文件和目录都使用 0 的 UID 和 GID 创建,除非可选`--chown`标志指定给定的用户名、组名或 UID/GID 组合以请求添加内容的特定所有权。该`--chown`标志的格式允许用户名和组名字符串或直接整数 UID 和 GID 的任意组合。提供不带组名的用户名或不带 GID 的 UID 将使用与 GID 相同的数字 UID。如果提供了用户名或组名,容器的根文件系统`/etc/passwd`和`/etc/group`文件将分别用于执行从名称到整数 UID 或 GID 的转换。以下示例显示了`--chown`标志的有效定义:
~~~
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
~~~
如果容器根文件系统不包含`/etc/passwd`或`/etc/group`文件,并且在`--chown`标志中使用了用户名或组名,则构建`ADD`操作将失败。使用数字 ID 不需要查找,也不依赖于容器根文件系统内容。
在 where`<src>`是远程文件 URL的情况下,目标将具有 600 的权限。如果正在检索的远程文件具有 HTTP`Last-Modified`标头,则来自该标头的时间戳将用于设置`mtime`目标文件上的 。但是,与在 期间处理的任何其他文件一样`ADD`,`mtime`将不包括在确定文件是否已更改和缓存是否应更新的确定中。
> **笔记**
>
> 如果通过传递`Dockerfile`STDIN (`docker build - < somefile`) 进行构建,则没有构建上下文,因此`Dockerfile`只能包含基于 URL 的`ADD`指令。您还可以通过 STDIN: (`docker build - < archive.tar.gz`)传递压缩档案,档案`Dockerfile`的根目录和档案的其余部分将用作构建的上下文。
如果您的 URL 文件使用身份验证保护,则需要使用`RUN wget`,`RUN curl`或使用容器内的其他工具,因为该`ADD`指令不支持身份验证。
> **笔记**
>
> `ADD`如果 Dockerfile 的内容`<src>`已更改,则遇到的第一个指令将使 Dockerfile 中所有后续指令的缓存无效。这包括使`RUN`指令的缓存无效。有关更多信息,请参阅[`Dockerfile`最佳实践指南 – 利用构建缓存](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache)。
`ADD`遵守以下规则:
* 该`<src>`路径必须是内部*语境*的构建;你不能`ADD ../something /something`,因为 a 的第一步`docker build`是将上下文目录(和子目录)发送到 docker 守护进程。
* 如果`<src>`是 URL 并且`<dest>`不以斜杠结尾,则从 URL 下载文件并将其复制到`<dest>`.
* 如果`<src>`是 URL 并且`<dest>`确实以斜杠结尾,则从 URL 推断文件名并将文件下载到`<dest>/<filename>`.例如,`ADD http://example.com/foobar /`将创建文件`/foobar`.URL 必须有一个重要的路径,以便在这种情况下可以发现适当的文件名(`http://example.com`将不起作用)。
* 如果`<src>`是目录,则复制目录的全部内容,包括文件系统元数据。
> **笔记**
>
> 不会复制目录本身,只会复制其内容。
* 如果`<src>`是可识别的压缩格式(identity、gzip、bzip2 或 xz)的*本地*tar 存档,则将其解压缩为目录。来自*远程*URL 的资源**不会被**解压缩。当一个目录被复制或解包时,它的行为与 相同`tar -x`,结果是:
1. 目标路径中存在的任何内容和
2. 源树的内容,解决了有利于“2”的冲突。在逐个文件的基础上。
> **笔记**
>
> 文件是否被识别为可识别的压缩格式完全取决于文件的内容,而不是文件的名称。例如,如果一个空文件恰好以`.tar.gz`this结尾,则不会将其识别为压缩文件,**也不会**生成任何类型的解压缩错误消息,而只会将该文件复制到目的地。
* 如果`<src>`是任何其他类型的文件,则将其与其元数据一起单独复制。在这种情况下,如果`<dest>`以斜杠结尾`/`,它将被视为一个目录,其内容`<src>`将被写入`<dest>/base(<src>)`。
* 如果`<src>`直接指定了多个资源,或者由于使用了通配符,则`<dest>`必须是目录,并且必须以斜杠结尾`/`。
* 如果`<dest>`不以斜杠结尾,则将其视为常规文件,并将其内容`<src>`写入`<dest>`.
* 如果`<dest>`不存在,则创建它及其路径中所有丢失的目录。
## COPY
COPY 有两种形式:
~~~
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
~~~
包含空格的路径需要后一种形式
> **笔记**
>
> 该`--chown`功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器。由于用户和组所有权概念不Linux和Windows,使用之间进行转换`/etc/passwd`,并`/etc/group`用于转换的用户和组名ID的限制此功能仅适用于基于Linux操作系统的容器是可行的。
该`COPY`指令从中复制新文件或目录`<src>`并将它们添加到容器的文件系统中的路径`<dest>`。
`<src>`可以指定多个资源,但文件和目录的路径将被解释为相对于构建上下文的源。
每个都`<src>`可能包含通配符,匹配将使用 Go 的[filepath.Match](http://golang.org/pkg/path/filepath#Match)规则完成。例如:
添加所有以“hom”开头的文件:
~~~
COPY hom* /mydir/
~~~
在下面的示例中,`?`被替换为任何单个字符,例如“home.txt”。
~~~
COPY hom?.txt /mydir/
~~~
`<dest>`是一个绝对路径,或相对于一个路径`WORKDIR`,到其中的源将在目标容器内进行复制。
下面的示例使用相对路径,并将“test.txt”添加到`<WORKDIR>/relativeDir/`:
~~~
COPY test.txt relativeDir/
~~~
而此示例使用绝对路径,并将“test.txt”添加到`/absoluteDir/`
~~~
COPY test.txt /absoluteDir/
~~~
复制包含特殊字符(如`[`和`]`)的文件或目录时,您需要按照 Golang 规则对这些路径进行转义,以防止它们被视为匹配模式。例如,要复制名为 的文件`arr[0].txt`,请使用以下命令;
~~~
COPY arr[[]0].txt /mydir/
~~~
所有新文件和目录都使用 0 的 UID 和 GID 创建,除非可选`--chown`标志指定给定的用户名、组名或 UID/GID 组合以请求复制内容的特定所有权。该`--chown`标志的格式允许用户名和组名字符串或直接整数 UID 和 GID 的任意组合。提供不带组名的用户名或不带 GID 的 UID 将使用与 GID 相同的数字 UID。如果提供了用户名或组名,容器的根文件系统`/etc/passwd`和`/etc/group`文件将分别用于执行从名称到整数 UID 或 GID 的转换。以下示例显示了`--chown`标志的有效定义:
~~~
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/
~~~
如果容器根文件系统不包含`/etc/passwd`或`/etc/group`文件,并且在`--chown`标志中使用了用户名或组名,则构建`COPY`操作将失败。使用数字 ID 不需要查找并且不依赖于容器根文件系统内容。
> **笔记**
>
> 如果使用 STDIN (`docker build - < somefile`)构建,则没有构建上下文,因此`COPY`无法使用。
可以选择`COPY`接受一个标志`--from=<name>`,该标志可用于将源位置设置为前一个构建阶段(使用`FROM .. AS <name>`),该阶段将用于代替用户发送的构建上下文。如果无法找到具有指定名称的构建阶段,则会尝试使用具有相同名称的图像。
`COPY`遵守以下规则:
* 该`<src>`路径必须是内部*语境*的构建;你不能`COPY ../something /something`,因为 a 的第一步`docker build`是将上下文目录(和子目录)发送到 docker 守护进程。
* 如果`<src>`是目录,则复制目录的全部内容,包括文件系统元数据。
> **笔记**
>
> 不会复制目录本身,只会复制其内容。
* 如果`<src>`是任何其他类型的文件,则将其与其元数据一起单独复制。在这种情况下,如果`<dest>`以斜杠结尾`/`,它将被视为一个目录,其内容`<src>`将被写入`<dest>/base(<src>)`。
* 如果`<src>`直接指定了多个资源,或者由于使用了通配符,则`<dest>`必须是目录,并且必须以斜杠结尾`/`。
* 如果`<dest>`不以斜杠结尾,则将其视为常规文件,并将其内容`<src>`写入`<dest>`.
* 如果`<dest>`不存在,则创建它及其路径中所有丢失的目录。
> **笔记**
>
> `COPY`如果 Dockerfile 的内容`<src>`已更改,则遇到的第一个指令将使 Dockerfile 中所有后续指令的缓存无效。这包括使`RUN`指令的缓存无效。有关更多信息,请参阅[`Dockerfile`最佳实践指南 – 利用构建缓存](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache)。
## ENTRYPOINT
ENTRYPOINT 有两种形式:
在*EXEC*的形式,这是优选的形式:
~~~
ENTRYPOINT ["executable", "param1", "param2"]
~~~
该*shell*形式:
~~~
ENTRYPOINT command param1 param2
~~~
An`ENTRYPOINT`允许您配置将作为可执行文件运行的容器。
例如,以下内容以默认内容启动 nginx,侦听端口 80:
~~~
$ docker run -i -t --rm -p 80:80 nginx
~~~
命令行参数 to`docker run <image>`将附加在*exec*表单中的所有元素之后`ENTRYPOINT`,并将覆盖所有使用`CMD`.这允许将参数传递给入口点,即,`docker run <image> -d`将`-d`参数传递给入口点。您可以`ENTRYPOINT`使用`docker run --entrypoint`标志覆盖指令。
所述*壳*形式防止任何`CMD`或`run`被使用命令行参数,但具有的缺点是你`ENTRYPOINT`将开始作为的子命令`/bin/sh -c`,其不通过信号。这意味着可执行文件将不是容器的`PID 1`\- 并且*不会*接收 Unix 信号 - 因此您的可执行文件不会收到`SIGTERM`来自`docker stop <container>`.
只有 中的最后一条`ENTRYPOINT`指令`Dockerfile`会起作用。
### Exec form ENTRYPOINT example
您可以使用*exec*形式`ENTRYPOINT`设置相当稳定的默认命令和参数,然后使用任一形式`CMD`设置更可能更改的其他默认值。
~~~
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
~~~
当您运行容器时,您可以看到这`top`是唯一的过程:
~~~
$ docker run -it --rm --name test top -H
top - 08:25:00 up 7:27, 0 users, load average: 0.00, 0.01, 0.05
Threads: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffers
KiB Swap: 1441840 total, 0 used, 1441840 free. 1324440 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top
~~~
要进一步检查结果,您可以使用`docker exec`:
~~~
$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 2.6 0.1 19752 2352 ? Ss+ 08:24 0:00 top -b -H
root 7 0.0 0.1 15572 2164 ? R+ 08:25 0:00 ps aux
~~~
您可以`top`使用`docker stop test`.
下面`Dockerfile`显示了使用`ENTRYPOINT`来在前台运行 Apache(即 as`PID 1`):
~~~
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
~~~
如果您需要为单个可执行文件编写启动脚本,您可以使用`exec`和`gosu`命令确保最终的可执行文件接收到 Unix 信号:
~~~
#!/usr/bin/env bash
set -e
if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"
if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi
exec gosu postgres "$@"
fi
exec "$@"
~~~
最后,如果您需要在关闭时进行一些额外的清理(或与其他容器通信),或者正在协调多个可执行文件,您可能需要确保`ENTRYPOINT`脚本接收 Unix 信号,将它们传递,然后执行还有一些工作:
~~~
#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too
# USE the trap if you need to also do manual cleanup after the service is stopped,
# or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM
# start service in background here
/usr/sbin/apachectl start
echo "[hit enter key to exit] or run 'docker stop <container>'"
read
# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop
echo "exited $0"
~~~
如果使用 运行此映像`docker run -it --rm -p 80:80 --name test apache`,则可以使用`docker exec`、 或来检查容器的进程`docker top`,然后让脚本停止 Apache:
~~~
$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.0 4448 692 ? Ss+ 00:42 0:00 /bin/sh /run.sh 123 cmd cmd2
root 19 0.0 0.2 71304 4440 ? Ss 00:42 0:00 /usr/sbin/apache2 -k start
www-data 20 0.2 0.2 360468 6004 ? Sl 00:42 0:00 /usr/sbin/apache2 -k start
www-data 21 0.2 0.2 360468 6000 ? Sl 00:42 0:00 /usr/sbin/apache2 -k start
root 81 0.0 0.1 15572 2140 ? R+ 00:44 0:00 ps aux
$ docker top test
PID USER COMMAND
10035 root {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054 root /usr/sbin/apache2 -k start
10055 33 /usr/sbin/apache2 -k start
10056 33 /usr/sbin/apache2 -k start
$ /usr/bin/time docker stop test
test
real 0m 0.27s
user 0m 0.03s
sys 0m 0.03s
~~~
> **笔记**
>
> 您可以使用 覆盖`ENTRYPOINT`设置`--entrypoint`,但这只能将二进制文件设置为*exec*(不会`sh -c`使用)。
> **笔记**
>
> 在*EXEC*形式被解析为一个JSON阵列,这意味着必须使用双引号(“)周围的话不单引号(')。
与*shell*形式不同,*exec*形式不调用命令 shell。这意味着不会发生正常的 shell 处理。例如,`ENTRYPOINT [ "echo", "$HOME" ]`不会对 进行变量替换`$HOME`。如果你想要 shell 处理,那么要么使用*shell*形式,要么直接执行 shell,例如:`ENTRYPOINT [ "sh", "-c", "echo $HOME" ]`.当使用 exec 形式并直接执行 shell 时,就像 shell 形式一样,是 shell 进行环境变量扩展,而不是 docker。
### Shell form ENTRYPOINT example
您可以为 指定一个纯字符串,`ENTRYPOINT`它将在`/bin/sh -c`.这种形式将使用 shell 处理来替换 shell 环境变量,并且将忽略任何`CMD`或`docker run`命令行参数。为确保正确`docker stop`发出任何长时间运行的`ENTRYPOINT`可执行文件的信号,您需要记住以以下方式启动它`exec`:
~~~
FROM ubuntu
ENTRYPOINT exec top -b
~~~
运行此映像时,您将看到单个`PID 1`进程:
~~~
$ docker run -it --rm --name test top
Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU: 5% usr 0% sys 0% nic 94% idle 0% io 0% irq 0% sirq
Load average: 0.08 0.03 0.05 2/98 6
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
1 0 root R 3164 0% 0% top -b
~~~
哪个干净地退出`docker stop`:
~~~
$ /usr/bin/time docker stop test
test
real 0m 0.20s
user 0m 0.02s
sys 0m 0.04s
~~~
如果您忘记添加`exec`到您的开头`ENTRYPOINT`:
~~~
FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1
~~~
然后您可以运行它(为下一步命名):
~~~
$ docker run -it --name test top --ignored-param2
Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached
CPU: 9% usr 2% sys 0% nic 88% idle 0% io 0% irq 0% sirq
Load average: 0.01 0.02 0.05 2/101 7
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
1 0 root S 3168 0% 0% /bin/sh -c top -b cmd cmd2
7 1 root R 3164 0% 0% top -b
~~~
您可以从输出中`top`看到指定`ENTRYPOINT`的不是`PID 1`。
如果您然后运行`docker stop test`,容器将不会干净地退出 - 该`stop`命令将`SIGKILL`在超时后强制发送:
~~~
$ docker exec -it test ps aux
PID USER COMMAND
1 root /bin/sh -c top -b cmd cmd2
7 root top -b
8 root ps aux
$ /usr/bin/time docker stop test
test
real 0m 10.19s
user 0m 0.04s
sys 0m 0.03s
~~~
### Understand how CMD and ENTRYPOINT interact
无论`CMD`和`ENTRYPOINT`指令定义运行的容器中时什么命令得到执行。很少有规则可以描述他们的合作。
1. Dockerfile 应至少指定`CMD`或`ENTRYPOINT`命令之一。
2. `ENTRYPOINT`应在将容器用作可执行文件时进行定义。
3. `CMD`应该用作定义`ENTRYPOINT`命令的默认参数或在容器中执行临时命令的一种方式。
4. `CMD`使用替代参数运行容器时将被覆盖。
下表显示了针对不同`ENTRYPOINT`/`CMD`组合执行的命令:
| | 没有入口点 | 入口点 exec\_entry p1\_entry | 入口点 \[“exec\_entry”,“p1\_entry”\] |
| --- | --- | --- | --- |
| **没有 CMD** | *错误,不允许* | /bin/sh -c exec\_entry p1\_entry | exec\_entry p1\_entry |
| **CMD \[“exec\_cmd”,“p1\_cmd”\]** | exec\_cmd p1\_cmd | /bin/sh -c exec\_entry p1\_entry | exec\_entry p1\_entry exec\_cmd p1\_cmd |
| **CMD \[“p1\_cmd”,“p2\_cmd”\]** | p1\_cmd p2\_cmd | /bin/sh -c exec\_entry p1\_entry | exec\_entry p1\_entry p1\_cmd p2\_cmd |
| **CMD exec\_cmd p1\_cmd** | /bin/sh -c exec\_cmd p1\_cmd | /bin/sh -c exec\_entry p1\_entry | exec\_entry p1\_entry /bin/sh -c exec\_cmd p1\_cmd |
> **笔记**
>
> 如果`CMD`从基本图像定义,则设置`ENTRYPOINT`将重置`CMD`为空值。在这种情况下,`CMD`必须在当前图像中定义一个值。
## VOLUME
~~~
VOLUME ["/data"]
~~~
该`VOLUME`指令创建一个具有指定名称的挂载点,并将其标记为保存来自本机主机或其他容器的外部挂载卷。该值可以是 JSON 数组、`VOLUME ["/var/log/"]`或带有多个参数的纯字符串,例如`VOLUME /var/log`或`VOLUME /var/log /var/db`。有关通过 Docker 客户端的更多信息/示例和安装说明,请参阅[*通过卷共享目录*](https://docs.docker.com/storage/volumes/)文档。
该`docker run`命令使用基础映像中指定位置存在的任何数据初始化新创建的卷。例如,考虑以下 Dockerfile 片段:
~~~
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
~~~
此 Dockerfile 会`docker run`生成一个映像,该映像会导致创建新的挂载点`/myvol`并将`greeting`文件复制到新创建的卷中。
### Notes about specifying volumes
请记住以下有关`Dockerfile`.
* **基于 Windows 的容器上的卷**:使用基于 Windows 的容器时,容器内卷的目标必须是以下之一:
* 一个不存在或空的目录
* 驱动器以外的驱动器`C:`
* **从 Dockerfile 中更改卷**:如果任何构建步骤在声明卷后更改了卷中的数据,则这些更改将被丢弃。
* **JSON 格式**:列表被解析为 JSON 数组。您必须用双引号 (`"`) 而不是单引号 (`'`)将单词括起来。
* **主机目录在容器运行时声明**:主机目录(挂载点)本质上是依赖于主机的。这是为了保持图像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。因此,您无法从 Dockerfile 中挂载主机目录。该`VOLUME`指令不支持指定`host-dir`参数。您必须在创建或运行容器时指定挂载点。
## USER
~~~
USER <user>[:<group>]
~~~
或
~~~
USER <UID>[:<GID>]
~~~
所述`USER`指令集运行的图像和用于任何时要使用的用户名(或UID)和任选的所述用户组(或GID)`RUN`,`CMD`和`ENTRYPOINT`它后面的指令`Dockerfile`。
> 请注意,为用户指定组时,用户将*仅*具有指定的组成员资格。任何其他配置的组成员资格都将被忽略。
> **警告**
>
> 当用户没有主要组时,映像(或下一个说明)将与该`root`组一起运行。
>
> 在 Windows 上,如果用户不是内置帐户,则必须先创建该用户。这可以通过`net user`作为 Dockerfile 的一部分调用的命令来完成。
~~~
FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick
~~~
## WORKDIR
~~~
WORKDIR /path/to/workdir
~~~
该`WORKDIR`指令集的工作目录对任何`RUN`,`CMD`,`ENTRYPOINT`,`COPY`和`ADD`它后面的说明`Dockerfile`。如果`WORKDIR`不存在,即使它没有在任何后续`Dockerfile`指令中使用,它也会被创建。
该`WORKDIR`指令可以在一个`Dockerfile`.如果提供了相对路径,它将相对于前一条`WORKDIR`指令的路径。例如:
~~~
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
~~~
最终`pwd`命令的输出`Dockerfile`将是`/a/b/c`.
该`WORKDIR`指令可以解析先前使用`ENV`.您只能使用在`Dockerfile`.例如:
~~~
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
~~~
最终`pwd`命令的输出`Dockerfile`将是`/path/$DIRNAME`
## ARG
~~~
ARG <name>[=<default value>]
~~~
该`ARG`指令定义了一个变量,用户可以在构建时`docker build`通过使用`--build-arg <varname>=<value>`标志的命令将其传递给构建器。如果用户指定了未在 Dockerfile 中定义的构建参数,则构建会输出警告。
~~~
[Warning] One or more build-args [foo] were not consumed.
~~~
一个 Dockerfile 可能包含一个或多个`ARG`指令。例如,以下是一个有效的 Dockerfile:
~~~
FROM busybox
ARG user1
ARG buildno
# ...
~~~
> **警告:**
>
> 不建议使用构建时变量来传递秘密,如 github 密钥、用户凭据等`docker history`。使用命令的图像的任何用户都可以看到构建时变量值。
>
> 请参阅[“使用 BuildKit 构建镜像”](https://docs.docker.com/develop/develop-images/build_enhancements/#new-docker-build-secret-information)部分,了解在构建镜像时使用机密的安全方法。
### Default values
一条`ARG`指令可以选择包含一个默认值:
~~~
FROM busybox
ARG user1=someuser
ARG buildno=1
# ...
~~~
如果`ARG`指令具有默认值并且在构建时没有传递任何值,则构建器使用默认值。
### Scope
一个`ARG`变量定义进入从在其上在限定的线效果`Dockerfile`不从参数对命令行或其他地方使用。例如,考虑这个 Dockerfile:
~~~
FROM busybox
USER ${user:-some_user}
ARG user
USER $user
# ...
~~~
用户通过调用构建此文件:
~~~
$ docker build --build-arg user=what_user .
~~~
的`USER`第2次的计算结果为`some_user`作为`user`变量的后续行3.定义`USER`4个求值在线路到`what_user`作为`user`被定义并且`what_user`值在命令行上通过。在`ARG`指令定义之前,对变量的任何使用都会导致空字符串。
的`ARG`指令在它被定义的构建阶段结束推移的范围进行。要在多个阶段使用 arg,每个阶段都必须包含`ARG`指令。
~~~
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS
~~~
### Using ARG variables
您可以使用`ARG`或`ENV`指令来指定可用于`RUN`指令的变量。使用`ENV`指令定义的环境变量总是覆盖`ARG`同名指令。考虑这个带有`ENV`and`ARG`指令的Dockerfile。
~~~
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER
~~~
然后,假设此映像是使用以下命令构建的:
~~~
$ docker build --build-arg CONT_IMG_VER=v2.0.1 .
~~~
在这种情况下,`RUN`指令使用`v1.0.0`而不是`ARG`用户传递的设置:`v2.0.1`此行为类似于 shell 脚本,其中本地范围的变量覆盖作为参数传递或从环境继承的变量,从定义的角度来看。
使用上面的例子,但不同的`ENV`规格,您可以创建更多的之间的互动非常有用`ARG`和`ENV`说明:
~~~
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
~~~
与`ARG`指令不同,`ENV`值始终保留在构建的映像中。考虑一个没有`--build-arg`标志的 docker 构建:
~~~
$ docker build .
~~~
使用此 Dockerfile 示例,`CONT_IMG_VER`仍保留在映像中,但其值将是指令`v1.0.0`在第 3 行中设置的默认值`ENV`。
本示例中的变量扩展技术允许您从命令行传递参数,并通过利用`ENV`指令将它们保留在最终图像中。变量扩展仅支持[有限的 Dockerfile 指令集。](https://docs.docker.com/engine/reference/builder/#environment-replacement)
### Predefined ARGs
Docker 有一组预定义的`ARG`变量,您可以`ARG`在 Dockerfile 中没有相应指令的情况下使用这些变量。
* `HTTP_PROXY`
* `http_proxy`
* `HTTPS_PROXY`
* `https_proxy`
* `FTP_PROXY`
* `ftp_proxy`
* `NO_PROXY`
* `no_proxy`
要使用这些,请使用`--build-arg`标志在命令行上传递它们,例如:
~~~
$ docker build --build-arg HTTPS_PROXY=https://my-proxy.example.com .
~~~
默认情况下,这些预定义变量从`docker history`.排除它们会降低意外泄漏`HTTP_PROXY`变量中敏感身份验证信息的风险。
例如,考虑使用以下 Dockerfile 构建`--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com`
~~~
FROM ubuntu
RUN echo "Hello World"
~~~
在这种情况下,`HTTP_PROXY`变量的值在中不可用`docker history`并且不会被缓存。如果您要更改位置,并且您的代理服务器更改为`http://user:pass@proxy.sfo.example.com`,则后续构建不会导致缓存未命中。
如果您需要覆盖此行为,则可以通过`ARG`在 Dockerfile 中添加如下语句来实现:
~~~
FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"
~~~
构建此 Dockerfile 时,将`HTTP_PROXY`保留在 中`docker history`,更改其值会使构建缓存无效。
### Automatic platform ARGs in the global scope
此功能仅在使用[BuildKit](https://docs.docker.com/engine/reference/builder/#buildkit)后端时可用。
Docker 预定义了一组`ARG`变量,其中包含有关执行构建的节点的平台(构建平台)和生成的映像的平台(目标平台)的信息。可以使用`--platform`on 标志指定目标平台`docker build`。
以下`ARG`变量是自动设置的:
* `TARGETPLATFORM`\- 构建结果的平台。例如`linux/amd64`,`linux/arm/v7`,`windows/amd64`。
* `TARGETOS`\- TARGETPLATFORM 的操作系统组件
* `TARGETARCH`\- TARGETPLATFORM 的架构组件
* `TARGETVARIANT`\- TARGETPLATFORM 的变体组件
* `BUILDPLATFORM`\- 执行构建的节点的平台。
* `BUILDOS`\- BUILDPLATFORM 的操作系统组件
* `BUILDARCH`\- BUILDPLATFORM 的架构组件
* `BUILDVARIANT`\- BUILDPLATFORM 的变体组件
这些参数在全局范围内定义,因此在构建阶段或您的`RUN`命令中不会自动可用。在构建阶段公开这些参数之一,重新定义它没有价值。
例如:
~~~
FROM alpine
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"
~~~
### Impact on build caching
`ARG`变量不会像`ENV`变量那样持久化到构建的映像中。但是,`ARG`变量确实以类似的方式影响构建缓存。如果 Dockerfile 定义了一个`ARG`变量,其值与之前的构建不同,那么“缓存未命中”发生在它第一次使用时,而不是它的定义。特别是,`RUN`指令后面的所有指令都隐式地`ARG`使用该`ARG`变量(作为环境变量),因此可能导致缓存未命中。所有预定义`ARG`变量免于缓存,除非有一个匹配`ARG`的语句中`Dockerfile`。
例如,考虑这两个 Dockerfile:
~~~
FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER
~~~
~~~
FROM ubuntu
ARG CONT_IMG_VER
RUN echo hello
~~~
如果`--build-arg CONT_IMG_VER=<value>`在命令行中指定,在这两种情况下,第 2 行的指定都不会导致缓存未命中;第 3 行确实会导致缓存未命中。`ARG CONT_IMG_VER`导致 RUN 行被标识为与 running 相同`CONT_IMG_VER=<value> echo hello`,因此如果`<value>`发生变化,我们会得到缓存未命中。
考虑同一命令行下的另一个示例:
~~~
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=$CONT_IMG_VER
RUN echo $CONT_IMG_VER
~~~
在此示例中,缓存未命中发生在第 3 行。发生未命中是因为`ENV`引用`ARG`变量中的变量值以及通过命令行更改了该变量。在此示例中,该`ENV`命令使图像包含该值。
如果一条`ENV`指令覆盖了一条`ARG`同名指令,比如这个 Dockerfile:
~~~
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=hello
RUN echo $CONT_IMG_VER
~~~
第 3 行不会导致缓存未命中,因为 的值为`CONT_IMG_VER`常数 (`hello`)。因此,在`RUN`(第 4 行)上使用的环境变量和值在构建之间不会改变。
## ONBUILD
~~~
ONBUILD <INSTRUCTION>
~~~
该`ONBUILD`指令将一个*触发*指令添加到图像中,以便稍后在该图像用作另一个构建的基础时执行。触发器将在下游构建的上下文中执行,就好像它是`FROM`在下游的指令之后立即插入的`Dockerfile`。
任何构建指令都可以注册为触发器。
如果您正在构建将用作构建其他镜像的基础的镜像,例如应用程序构建环境或可以使用用户特定配置自定义的守护程序,这将非常有用。
例如,如果您的映像是可重用的 Python 应用程序构建器,则需要将应用程序源代码添加到特定目录中,并且可能需要*在*此*之后*调用构建脚本。您不能只调用`ADD`and`RUN`now,因为您还没有访问应用程序源代码的权限,而且每个应用程序构建都会有所不同。您可以简单地为应用程序开发人员提供一个样板,`Dockerfile`以便将其复制粘贴到他们的应用程序中,但这效率低下、容易出错且难以更新,因为它与特定于应用程序的代码混合在一起。
解决方案是`ONBUILD`在下一个构建阶段使用注册高级指令以便稍后运行。
这是它的工作原理:
1. 当遇到`ONBUILD`指令时,构建器会向正在构建的图像的元数据添加触发器。该指令不会以其他方式影响当前构建。
2. 在构建结束时,所有触发器的列表存储在图像清单中的键下`OnBuild`。可以使用`docker inspect`命令检查它们。
3. 稍后,可以使用该`FROM`指令将该映像用作新构建的基础。作为处理`FROM`指令的一部分,下游构建器查找`ONBUILD`触发器,并按照它们注册的顺序执行它们。如果任何触发器失败,`FROM`指令就会中止,从而导致构建失败。如果所有触发器都成功,则`FROM`指令完成并且构建照常继续。
4. 触发器在执行后从最终图像中清除。换句话说,它们不是由“孙子”构建继承的。
例如,您可以添加如下内容:
~~~
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
~~~
> **警告**
>
> 不允许`ONBUILD`使用链接指令`ONBUILD ONBUILD`。
> **警告**
>
> 该`ONBUILD`指令可能不会触发`FROM`或`MAINTAINER`指令。
## STOPSIGNAL
~~~
STOPSIGNAL signal
~~~
该`STOPSIGNAL`指令设置将发送到容器退出的系统调用信号。该信号可以是与内核系统调用表中的位置匹配的有效无符号数,例如 9,或格式为 SIGNAME 的信号名称,例如 SIGKILL。
## HEALTHCHECK
该`HEALTHCHECK`指令有两种形式:
* `HEALTHCHECK [OPTIONS] CMD command`(通过在容器内运行命令来检查容器健康状况)
* `HEALTHCHECK NONE`(禁用从基础镜像继承的任何健康检查)
该`HEALTHCHECK`指令告诉 Docker 如何测试容器以检查它是否仍在工作。这可以检测诸如 Web 服务器陷入无限循环并且无法处理新连接的情况,即使服务器进程仍在运行。
当容器指定了*健康检查时*,除了其正常状态之外,它还具有*健康*状态。此状态最初为`starting`。每当健康检查通过时,它就会变成`healthy`(无论它之前处于什么状态)。连续失败一定次数后,变为`unhealthy`。
之前可以出现的选项`CMD`有:
* `--interval=DURATION`(默认值:`30s`)
* `--timeout=DURATION`(默认值:`30s`)
* `--start-period=DURATION`(默认值:`0s`)
* `--retries=N`(默认值:`3`)
运行状况检查将在容器启动后首先运行**interval**seconds,然后在每次之前的检查完成后再次运行**interval**seconds。
如果检查的单次运行时间超过**timeout**秒,则认为检查失败。
它需要**重试**连续的健康检查失败才能考虑容器`unhealthy`。
**start period**为需要时间引导的容器提供初始化时间。在此期间的探测失败将不计入最大重试次数。但是,如果在启动期间健康检查成功,则认为容器已启动,所有连续失败都将计入最大重试次数。
`HEALTHCHECK`一个 Dockerfile 中只能有一条指令。如果您列出多个,则只有最后一个`HEALTHCHECK`才会生效。
`CMD`关键字后的命令可以是 shell 命令(例如`HEALTHCHECK CMD /bin/check-running`)或*exec*数组(与其他 Dockerfile 命令一样;`ENTRYPOINT`有关详细信息,请参见 eg)。
命令的退出状态指示容器的健康状态。可能的值为:
* 0:成功 - 容器运行良好,可以使用
* 1:不健康 - 容器无法正常工作
* 2:reserved - 不要使用这个退出代码
例如,每五分钟左右检查一次网络服务器是否能够在三秒钟内为站点的主页提供服务:
~~~
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
~~~
为了帮助调试失败的探测,命令在 stdout 或 stderr 上写入的任何输出文本(UTF-8 编码)都将存储在健康状态中,并且可以使用`docker inspect`.此类输出应保持简短(当前仅存储前 4096 个字节)。
当容器的健康状态发生变化时,`health_status`会生成具有新状态的事件。
## SHELL
~~~
SHELL ["executable", "parameters"]
~~~
该`SHELL`指令允许覆盖用于命令的*shell*形式的默认 shell。Linux 上的默认 shell 是`["/bin/sh", "-c"]`,Windows 上是`["cmd", "/S", "/C"]`.该`SHELL`指令*必须*以 JSON 格式写入 Dockerfile。
该`SHELL`指令在 Windows 上特别有用,因为 Windows 有两种常用且截然不同的本机 shell:`cmd`和`powershell`,以及可用的备用 shell,包括`sh`.
该`SHELL`指令可以出现多次。每条`SHELL`指令都会覆盖所有先前的`SHELL`指令,并影响所有后续指令。例如:
~~~
FROM microsoft/windowsservercore
# Executed as cmd /S /C echo default
RUN echo default
# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default
# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello
# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello
~~~
`SHELL`当在 Dockerfile 中使用它们的*shell*形式时,以下指令可能会受到该指令的影响:`RUN`,`CMD`和`ENTRYPOINT`.
以下示例是在 Windows 上找到的常见模式,可以使用`SHELL`指令进行精简:
~~~
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
~~~
docker 调用的命令将是:
~~~
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
~~~
这是低效的,原因有二。首先,调用了一个不必要的 cmd.exe 命令处理器(又名 shell)。其次,*shell*形式`RUN`中的每条指令都需要一个额外的命令前缀。`powershell -command`
为了使这更有效,可以采用两种机制之一。一种是使用 RUN 命令的 JSON 形式,例如:
~~~
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
~~~
虽然 JSON 形式是明确的并且不使用不必要的 cmd.exe,但它确实需要通过双引号和转义来更加冗长。另一种机制是使用`SHELL`指令和*外壳*形式,为 Windows 用户提供更自然的语法,尤其是与`escape`解析器指令结合使用时:
~~~
# escape=`
FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'
~~~
导致:
~~~
PS E:\myproject> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
---> Running in 6fcdb6855ae2
---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
---> Running in d0eef8386e97
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 10/28/2016 11:26 AM Example
---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
---> Running in be6d8e63fe75
hello world
---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\myproject>
~~~
该`SHELL`指令还可用于修改 shell 的运行方式。例如,`SHELL cmd /S /C /V:ON|OFF`在 Windows 上使用,可以修改延迟的环境变量扩展语义。
如果`SHELL`需要备用 shell,例如、和其他`zsh`,该指令也可以在 Linux 上使用。`csh tcsh`
## Dockerfile examples
您可以在下面看到 Dockerfile 语法的一些示例。
~~~
# Nginx
#
# VERSION 0.0.1
FROM ubuntu
LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
~~~
~~~
# Firefox over VNC
#
# VERSION 0.3
FROM ubuntu
# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN apt-get update && apt-get install -y x11vnc xvfb firefox
RUN mkdir ~/.vnc
# Setup a password
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way, but it does the trick)
RUN bash -c 'echo "firefox" >> /.bashrc'
EXPOSE 5900
CMD ["x11vnc", "-forever", "-usepw", "-create"]
~~~
~~~
# Multiple images example
#
# VERSION 0.1
FROM ubuntu
RUN echo foo > bar
# Will output something like ===> 907ad6c2736f
FROM ubuntu
RUN echo moo > oink
# Will output something like ===> 695d7793cbe4
# You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
# /oink.
~~~
有关 Dockerfile 的示例,请参阅:
* 在[“build images”](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)
* 在[“开始”](https://docs.docker.com/get-started/)
* 该[语言特定的入门指南](https://docs.docker.com/language/)