Dockerfile 参考文档

Docker可以通过读取Dockerfile中的指令来自动构建镜像。Dockerfile是一个文本文档,包含用户可以在命令行上调用的所有命令来组装一个镜像。使用docker build用户可以创建一个连续执行多个命令行指令的自动构建。 本页描述可在Dockerfile中使用的命令。 阅读完此页面后,请参阅Dockerfile最佳实践以获取技巧方面的指南。

Usage

docker build命令从Dockerfile上下文构建一个镜像。构建的上下文是位于指定位置PATHURL下的文件集。PATH是本地文件系统上的一个目录。URL是一个Git仓库位置。

docker上下文被递归地处理。因此,PAth包含任何子目录,URL包含仓库及其子模块。这个例子显示了一个build命令,它使用当前目录作为上下文:

$ docker build .
Sending build context to Docker daemon  6.51 MB
...

构建由Docker后台进程执行,而不是由CLI执行。构建流程要做的第一件事是将整个上下文(递归地)发送给后台进程。在大多数情况下,最好从一个空目录作为上下文开始,并将Dockerfile保存在该目录中。只添加构建Dockerfile所需的文件。

警告:不要使用根目录/作为PATH,因为它会导致构建时将硬盘驱动器的全部内容传输到Docker后台进程。

要在构建上下文中使用文件,Dockerfile在一条指令中引用这个文件,例如一条COPY指令。要提高构建的性能,可以通过在上下文目录中添加.dockerignore文件来排除文件和目录。有关如何创建.dockerignore文件的信息,本文的后面部分后详细阐述。

默认情况下,Dockerfile位于上下文的根目录下,文件名为Dockerfile。在docker build中可以使用-f标记指向文件系统中任何位置的Dockerfile。

$ docker build -f /path/to/a/Dockerfile .

如果构建成功,可以指定仓库和标签保存新镜像:

$ docker build -t shykes/myapp .

想要在构建之后将镜像标记并保存到多个仓库中,请在运行build命令时添加多个-t参数:

$ 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最佳实践指南中的Build cache部分:

$ 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指定的镜像不需要父链,可以从其他注册中心拉取。

完成构建之后,就可以考虑将仓库推送到它的注册中心

BuildKit

从18.09版本开始,Docker支持moby/buildkit项目提供的用于执行构建的新后端。与旧的实现相比,BuildKit后端提供了许多优点。例如,BuildKit可以:

  • 检测并跳过未使用的构建阶段
  • 并行化构建独立的构建阶段
  • 在构建上下文中,只在构建之间增量地传输已更改的文件
  • 检测并不传输在构建上下文中未使用的文件
  • 使用带有许多新特性的外部Dockerfile实现
  • 避免其他API(中间镜像和容器)的副作用
  • 为自动修剪设置构建缓存的优先级 要使用BuildKit后端,您需要在调用docker build之前在CLI上设置一个环境变量DOCKER_BUILDKIT=1

要了解基于BuildKit构建可用的实验性Dockerfile语法,请参考BuildKit仓库中的文档

格式

以下是Dockerfile的格式:

# Comment
INSTRUCTION arguments

指令不区分大小写。然而,习惯把它们写成大写的,以便更容易地将它们与参数区分开来。

Docker按顺序执行Dockerfile中指令。Dockerfile必须以FROM指令开始。它可能在解析器指令,注释和全局范围的ARGs之后。FROM指令指定正在构建的镜像的基镜像。FROM之前可能会有一个或多个ARG指令,它们声明在Dockerfile中的FROM行中使用的参数。

Docker将以#开头的行作为注释,除非该行是一个有效的解析器指令。行中任何位置的#标记都被视为参数。它允许这样的语句:

# Comment
RUN echo 'we are running some # of cool things'

注释中不支持行延续字符。

解析器指令

解析器指令是可选的,并且影响Dockerfile中后续行的处理方式。解析器指令不向构建中添加层,也不会被当做构建步骤。解析器指令被编写为一种特殊类型的注释,形式为# 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后端时才启用此功能。

syntax指令定义构建当前Dockerfile使用的Dockerfile构建器的位置。BuildKit后端允许无缝地使用构建器的外部实现,这些构建器作为Docker镜像分发,并在容器沙箱环境中执行。

自定义Dockerfile实现允许您:

  • 在不更新后台进程的情况下自动获得错误修复
  • 确保所有用户都使用相同的实现来构建Dockerfile
  • 在不更新后台进程的情况下使用最新的特性
  • 尝试新的实验性或第三方特性

官方版本

Docker分发官方版本的镜像,在Docker Hub上的docker/dockerfile仓库下可以用来构建Dockerfile。发布新镜像有两种通道:稳定的和实验性的。

稳定通道遵循语义版本控制。例如:

  • docker/dockerfile:1.0.0 -只允许不可变版本1.0.0
  • docker/dockerfile:1.0 -允许1.0.*版本
  • docker/dockerfile:1 -允许版本1..
  • docker/dockerfile:在稳定通道上的最新版本

实验通道在发布时使用来自稳定通道的主组件和子组件的增量版本控制。例如:

  • docker/dockerfile:1.0.1-experimental -只允许1.0.1-experimental不可变版本
  • docker/dockerfile:1.0-experimental - 1.0之后的最新实验版本
  • docker/dockerfile:experimental -实验通道的最新版本

你应该选择一个最适合你需要的通道。如果只想修复错误,应该使用docker/dockerfile:1.0。如果您想使用实验特性,您应该使用实验通道。如果您正在使用实验性通道,较新的版本可能不向后兼容,因此建议使用不可变的完整版本变体。

对于master构建和nightly发布的特性,请参考源仓库中的描述。

转义

# escape=\ (backslash)

或者

# escape=` (backtick)

escape指令用于设置Dockerfile中用来转义字符的字符。默认的转义字符为\

转义字符既用于转义行中的字符,也用于转义换行符。这允许Dockerfile指令跨越多行。请注意,无论Dockerfile中是否包含escape解析器指令,都不会在RUN命令中执行转义,除非在行尾

将转义字符设置为\`Windows上特别有用,其中\是目录路径分隔符。 \`与Windows PowerShell一致。

请考虑以下示例,该示例在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>

上面的例子的一个解决方案是使用/作为COPY指令和dir的目标。 但是,这种语法令人困惑的,因为它不是Windows原生的路径分隔符,并且容易出错,因为并不是所有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>

替换环境变量

环境变量使用ENV语句声明,也可以在某些指令中被Dockerfile解释为变量。声明中包含的变量这类语法也会按字面转义处理。

环境变量在Dockerfile中被写成$variable_name${variable_name}。它们被等效地处理,并且括号语法通常用于解决变量名称不能有空格的问题,例如${foo}_bar

${variable_name}语法还支持一些标准bash修饰符,如下所示:

  • ${variable-word}表示如果设置了variable,那么结果将是该值。如果未设置variable,那么word将是结果。
  • ${variable:+word}表示如果设置了variable,那么word将是结果,否则结果是空字符串。

在所有情况下,word可以是任何字符串,包括其他环境变量。

可以通过在变量之前添加\来进行转义,例如,\$foo\${foo}将分别按字面量转换为$foo${foo}

示例(解析后的表示显示在#之后):

FROM busybox
ENV foo /bar
WORKDIR ${foo}   # WORKDIR /bar
ADD . $foo       # ADD . /bar
COPY \$foo /quux # COPY $foo /quux

以下列表的DcokerFile指令支持环境变量:

  • ADD

  • COPY

  • ENV

  • EXPOSE

  • FROM

  • LABEL

  • STOPSIGNAL

  • USER

  • VOLUME

  • WORKDIR 也包括:

  • ONBUILD(当与上面支持的指令之一结合使用时)

    注意:在1.4之前,ONBUILD指令支持环境变量,即使与上面列出的任何指令结合使用也是如此。

环境变量替换将在整个指令中对每个变量使用相同的值。 换句话说,在这个例子中:

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

这将导致def值为hello,而不是bye。 但是ghi值为bye,因为它不是和设置abcbye的同一个指令。

.dockerigrnore文件

在docker CLI将上下文发送到docker后台程序之前,它会在上下文的根目录中查找名为.dockerignore的文件。如果此文件存在,CLI将修改上下文以排除其中的与它模式匹配的文件和目录。这有助于避免不必要地将大型或敏感文件和目录发送到后台程序,并可能使用ADDCOPY将它们添加到镜像。

CLI将.dockerignore文件被解释为行分隔模式的列表,类似于Unix shell的文件globs。出于匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式/foo/barfoo/bar将会排出PATHfoo子目录中名为bar的文件或目录,或位于git仓库URL的相应位置的文件或目录。

如果.dockerignore文件中的一行第1列以#开头,则该行被视为注释,并在CLI解释之前被忽略。

这是一个示例.dockerignore文件:

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

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

规则 行为
# comment 忽略
/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.Match规则,Docker还支持一个特殊的通配符字符串**,它匹配任意数量的目录(包括零)。 例如,**/*.go将排除在所有目录中找到的以.go结尾的所有文件,包括构建上下文的根。

!(感叹号)开头的行可用于定义排除例外。 以下是使用此机制的示例.dockerignore文件:

    *.md
    !README.md

README.md之外的所有markdown文件都将从上下文中排除。

!例外规则的位置会影响最终行为:与特定文件匹配的.dockerignore的最后一行确定是包含还是排除。 请考虑以下示例:

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

上下文中不包含markdown文件,除了README-secret.md以外的README文件。

现在考虑这个例子:

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

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

您甚至可以使用.dockerignore文件来排除Dockerfile.dockerignore文件。 这些文件仍然发送到后台程序,因为需要它们来完成它的工作。 但ADDCOPY指令不会将它们复制到镜像中。

最后,您可能希望指定要包含在上下文中的文件,而不是要排除的文件。 要实现此目的,请指定*作为第一个规则,然后指定一个或多个!例外模式。

注意:由于历史原因,规则.被忽略。

FROM

FROM <image> [AS <name>]

或者

FROM <image>[:<tag>] [AS <name>]

或者

FROM <image>[@<digest>] [AS <name>]

FROM指令初始化新的构建阶段并为后续指令设置基镜像。因此,有效的Dockerfile必须以FROM指令开头。镜像可以是任何有效镜像——通过从公共仓库拉取镜像来启动它尤其容易。

  • ARGDockerfile中唯一可以在FROM之前的指令。请参阅本文后面的 理解ARGFROM如何交互。

  • FROM可以在单个Dockerfile中多次出现以创建多个镜像,或者使用一个构建阶段作为另一个构建阶段的依赖项。只需在每个新的FROM指令之前记下提交输出的最后一个镜像ID。每个FROM指令清除先前指令创建的任何状态。

  • (可选)通过将AS name添加到FROM指令,可以将名称赋予新的构建阶段。该名称可以在后续的FROMCOPY --from=<name|index>指令中使用,以引用此阶段构建的镜像。

  • tagdigest值是可选的。如果省略其中任何一个,则构建器默认采用latest。如果找不到tag值,构建器将返回错误。

理解ARG和FROM如何交互

FROM指令支持在第一个FROM之前的任何ARG指令声明的变量。

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

FROM之前声明的ARG在构建阶段之外,因此在FROM之后的任何指令中都不能使用它。要使用在第一个FROM之前声明的ARG的默认值,请在构建阶段内指定ARG指令,并且ARG不为指定值:

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN

RUN有两种形式:

  • RUN <command>shell形式,命令在shell中运行,默认情况下是Linux上的/bin/sh -c或Windows上的cmd /S /C
  • RUN [“可执行文件”,“param1”,“param2”]exec形式)

RUN指令将在当前镜像之上的新层中执行任何命令并提交结果。 生成的已提交镜像将用于Dockerfile中的下一步。

分层RUN指令和生成提交符合Docker的核心概念,其中提交很便宜,并且可以从镜像历史中的任何点创建容器,很像源代码控制。

exec形式可以避免shell字符串苦恼,并使用不包含指定shell可执行文件的基本镜像来运行RUN命令。

可以使用SHELL命令更改shell形式的默认shell。

在shell形式中,您可以使用\(反斜杠)将单个RUN指令延续到下一行。 例如,请考虑以下两行:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

它们一起相当于这一行:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

**注意:**要使用不同于“/bin/sh”的shell,请使用传入所需shell的exec形式。 例如,RUN ["/bin/bash","-c","echo hello"]

**注意:**exec形式被解析为JSON数组,这意味着您必须使用双引号(")来包围单词而不是单引号(')。

注意:与shell形式不同,exec形式不会调用命令shell。 这意味着不会发生正常的shell处理。 例如,RUN ["echo", "$HOME"]不会对$HOME执行变量替换。 如果你想要shell处理,那么要么使用shell形式,要么直接执行shell,例如:RUN ["sh","-c","echo $HOME"]。 当使用exec形式并直接执行shell时,由shell执行环境变量解释,而不是docker。

**注意:**在JSON形式中,必须转义反斜杠。 这在反斜杠是路径分隔符的Windows上尤为重要。 由于不是有效的JSON,以下行将被视为shell形式,并以意外方式失败:RUN ["c:\windows\system32\tasklist.exe"]此示例的正确语法是:RUN ["C:\\windows\\SYSTEM32\\tasklist.exe"]

在下一次构建期间,RUN指令的缓存不会自动失效。 像RUN apt-get dist-upgrade -y这样的指令的缓存将在下一次构建期间重用。 可以使用--no-cache标记使RUN指令的高速缓存无效,例如docker build --no-cache

有关详细信息,请参阅Dockerfile最佳实践指南

ADD指令可以使RUN指令的缓存无效。 请参阅下文了解详情。

已知问题(RUN)

  • 问题783是关于使用AUFS文件系统时可能发生的文件权限问题。 例如,您可能会在尝试rm文件时注意到它。

对于具有最新aufs版本的系统(i.e.,可以设置dirperm1挂载选项),docker将尝试通过使用dirperm1选项加载层来自动修复问题。 有关dirperm1选项的更多详细信息,请参见aufs手册

如果您的系统不支持dirperm1,则问题描述了一种解决方法。

CMD

CMD指令有三种形式:

  • CMD ["executable","param1","param2"] (exec形式, 这是首选形式)
  • CMD ["param1","param2"] (作为ENTRYPOINT的默认参数)
  • CMD command param1 param2 (shell形式)

Dockerfile中只能有一条CMD指令。如果列出多个CMD,则只有最后一个CMD才会生效。

**CMD的主要目的是为执行容器提供默认值。**这些默认值可以包含可执行文件,也可以省略可执行文件,这时,您还必须指定ENTRYPOINT指令。

**注意:**如果使用CMDENTRYPOINT指令提供默认参数,应使用JSON数组形式。

**注意:**exec形式被解析为JSON数组,这意味着您必须使用双引号(")来包围单词而不是单引号(')。

注意:与shell形式不同,exec形式不会调用命令shell。这意味着不会发生正常的shell处理。例如,CMD ["echo","$HOME"]不会对$HOME进行变量替换。如果你想要shell处理,那么要么使用shell形式,要么直接执行shell,例如:CMD ["sh","-c","echo $HOME"]。当使用exec形式并直接执行shell时,由shell执行环境变量解释,而不是docker。

在shell或exec形式中使用时,CMD指令设置运行镜像时要执行的命令。

如果你使用CMD的shell形式,那么<command>将在/bin/sh -c中执行:

FROM ubuntu
CMD echo "This is a test." | wc -

如果要在没有shell的情况下运行<command>,则必须将该命令以JSON数组的形式并提供可执行文件的完整路径。此数组形式是CMD的首选格式。任何其他参数必须在数组中单独表示为字符串:

FROM ubuntu
CMD ["/usr/bin/wc","--help"]

如果您希望容器每次都运行相同的可执行文件,那么您应该考虑将ENTRYPOINT与CMD结合使用。请参阅本文稍后讲述的ENTRYPOINT。

如果用户指定了docker run的参数,那么它们将覆盖CMD中指定的默认值。

注意:不要将RUNCMD混淆。 RUN实际上运行一个命令并提交结果; CMD在构建时不执行任何操作,但指定了镜像的预期命令。

LABEL

LABEL <key>=<value> <key>=<value> <key>=<value> ...

LABEL指令将元数据添加到镜像。 LABEL是键值对。 要在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."

镜像可以有多个标签。 您可以在一行中指定多个标签。 在Docker 1.10之前,这减小了最终镜像的大小,但现在不需要这样了。 您仍然可以选择在单个指令中指定多个标签,方法有以下两种:

LABEL multi.label1="value1" multi.label2="value2" other="value3"

LABEL multi.label1="value1"       multi.label2="value2"       other="value3"

基镜像或父镜像(FROM行中的镜像)中包含的标签会被继承。如果标签已存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值。

要查看镜像的标签,请使用docker inspect命令。

"Labels": {
    "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指令设置生成镜像的Author字段。 使用LABEL指令更加灵活,您应该使用它,因为它可以设置您需要的任何元数据,并且可以轻松查看,例如使用docker inspect。 要设置与MAINTAINER字段对应的标签,您可以使用:

LABEL maintainer="SvenDowideit@home.org.au"

这将在docker inspect与其他标签一起显示。

EXPOSE

EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令通知Docker容器在运行时侦听指定的网络端口。 您可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认为TCP。

EXPOSE指令实际上不会发布端口。 它在构建镜像的人和运行容器的人之间起到一种文档的作用,关于哪些端口要发布。 要在运行容器时实际发布端口,请在docker run上使用-p标记发布和映射一个或多个端口,或使用-P标记发布所有公开的端口并将它们映射到高阶端口。

默认情况下,EXPOSE假定为TCP。 您还可以指定UDP:

EXPOSE 80/udp

要在TCP和UDP上公开,请包含两行:

EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果将-Pdocker run一起使用,则端口将为TCP公开一次,对UDP公开一次。 请记住,-P在主机上使用短暂的高阶主机端口,因此TCP和UDP的端口不同。

无论EXPOSE设置如何,您都可以使用-p标记在运行时覆盖它们。 例如

docker run -p 80:80/tcp -p 80:80/udp ...

要在主机系统上设置端口重定向,请参阅稍后讲述的 使用-P标记。 docker network命令支持创建用于容器之间通信的网络,而无需公开或发布特定端口,因为连接到网络的容器可以通过任何端口相互通信。 有关详细信息,请参阅此功能的概述

ENV

ENV <key> <value>
ENV <key>=<value> ...

ENV指令将环境变量<key>设置为值<value>。 此值将在构建阶段中的所有后续指令的环境中,并且也可以进行许多 内联替换(本文稍后会详细讲述)。

ENV指令有两种形式。 第一种形式是ENV <key> <value>,将单个变量设置为一个值。 第一个空格后面的整个字符串将被视为<value>——包括空格字符。 该值直到其他环境变量,因此将删除引号字符,如果未对其进行转义。

第二种形式ENV <key>=<value> ...允许一次设置多个变量。 请注意,第二种形式在语法中使用等号(=),而第一种形式不用。 与命令行解析一样,引号和反斜杠可用于在值内包含空格。

例如:

ENV myName="John Doe" myDog=Rex\ The\ Dog     myCat=fluffy

ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

将在最终镜像中产生相同的结果。

当从生成的镜像运行容器时,使用ENV设置的环境变量将保持不变。 您可以使用docker inspect查看值,并使用docker run --env <key>=<value>更改它们。

注意:环境持久性可能会导致意外的副作用。 例如,设置ENV DEBIAN_FRONTEND noninteractive可能会使基于Debian的镜像上的apt-get用户感到困惑。 要为单个命令设置值,请使用RUN <key>=<value> <command>

ADD

Add有两种形式:

  • ADD [--chown=<user>:<group>] <src>... <dest>
  • ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] (这种形式在路径包含空格的时候使用)

注意:--chown功能仅在用于构建Linux容器的Dockerfiles上受支持,并且不适用于Windows容器。 由于用户和组所有权概念不能在Linux和Windows之间进行转换,使用了/etc/passwd/etc/group将用户名和组名转换为ID会限制此功能仅适用于基于Linux OS的容器。

ADD指令从<src>复制新文件,目录或远程文件URL,并将它们添加到路径为<dest>的镜像文件系统中。

可以指定多个<src>资源,但如果它们是文件或目录,则它们的路径将被解释为相对于构建上下文。

每个<src>可能包含通配符,匹配将使用Go的filepath.Match规则完成。 例如:

ADD hom* /mydir/        # adds all files starting with "hom"
ADD hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"

<dest>是绝对路径或相对于WORKDIR的路径,源将被复制到目标容器中的该路径。

ADD test relativeDir/          # adds "test" to `WORKDIR`/relativeDir/
ADD test /absoluteDir/         # adds "test" to /absoluteDir/

添加的文件或目录包含特殊字符(例如[])时,需要按照Golang规则转义这些路径,以防止它们被视为匹配模式。 例如,要添加名为arr[0].txt的文件,请使用以下命令:

ADD arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/

所有新文件和目录创建时UID和GID都为0,除非使用可选的--chown标记指定用户名,组名或UID/GID组合以请求添加特定所有权的内容。--chown标记的形式允许用户名和组名字符串或任意组合的UID和GID。 提供没有组名的用户名或没有GID的UID将使用UID相同的数字作为GID。 如果提供了用户名或组名,则容器的根文件系统/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不需要查找,也不依赖于容器根文件系统内容。

<src>是远程文件URL的情况下,目标将具有600的权限。如果正在检索的远程文件具有HTTP Last-Modified header,则该header的时间戳将用于设置目标上的mtime文件。但是,与ADD期间处理的任何其他文件一样,mtime将用于确定文件是否已更改且应更新到缓存中。

**注意:**如果通过STDIN(docker build - <somefile>)传递Dockerfile来构建,则没有构建上下文,因此Dockerfile只能包含基于URL的ADD指令。您还可以通过STDIN传递压缩存档:(docker build -<archive.tar.gz>),Dockerfile位于存档的根目录下,存档的其余部分将用作构建的上下文。

**注意:**如果您的URL文件需要身份验证,则使用RUN wgetRUN curl或使用容器内的其他工具,因为ADD指令不支持身份验证。

注意:,如果遇到的ADD指令的<src>的内容已更改,将使来自Dockerfile的所有后续指令的缓存无效。这包括使RUN指令的缓存无效。有关详细信息,请参阅Dockerfile最佳实践指南

ADD遵守以下规则:

  • <src>路径必须位于构建的上下文中;你不能ADD ../something/something,因为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存档,来自远程的资源不能解压缩。则将其解压缩为目录。复制或解压缩目录时,它与tar -x具有相同的行为,结果是:

    • 目标路径上存在的内容,
    • 源文件的内容树,逐个文件用“2.”的方式解决冲突。

注意:文件是否被标识为可识别的压缩格式仅基于文件的内容而不是文件的名称来完成。例如,如果空文件恰好以.tar.gz结尾,则不会将其识别为压缩文件,也不会生成任何类型的解压缩错误消息,而是将文件简单地复制到目标。

  • 如果<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容器的Dockerfiles上受支持,不适用于Windows容器。 由于用户和组所有权概念不能在Linux和Windows之间进行转换,因为使用/etc/passwd/etc/group将用户名和组名转换为ID会限制此功能仅适用于基于Linux OS的容器。

COPY指令从<src>复制新文件或目录,并将它们添加到容器的文件系统中的路径<dest>

可以指定多个<src>资源,但文件和目录的路径将被解释为相对于构建上下文。

每个<src>可能包含通配符,匹配将使用Go的filepath.Match规则完成。 例如:

COPY test relativeDir/   # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/  # adds "test" to /absoluteDir/

<dest>是绝对路径,或相对于WORKDIR的路径,源将复制到目标容器内的该路径。

复制包含特殊字符(例如[])的文件或目录时,需要按照Golang规则转义这些路径,以防止它们被视为匹配模式。 例如,要复制名为arr[0].txt的文件,请使用以下命令:

COPY arr[[]0].txt /mydir/    # 复制文件为 "arr[0].txt" 到 /mydir/

将使用UID和GID为0创建新文件和目录,除非可选的--chown标记指定给定用户名,组名或UID/GID组合以请求复制内容的特定所有权。 --chown标记的形式允许用户名和组名字符串或任意组合的UID和GID。 提供没有组名的用户名或没有GID的UID将使用与UID作为GID。 如果提供了用户名或组名,则容器的/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|index>,可用于将源位置设置为先前的构建阶段(使用FROM .. AS <name>创建),而不是由用户发送的构建上下文。该标记还接受为使用FROM指令启动的所有先前构建阶段的索引。如果找不到具有指定名称的构建阶段,则尝试使用具有相同名称的镜像。

COPY遵守以下规则:

  • <src>路径必须位于构建的上下文中;你不能COPY ../something /something,因为docker build的第一步是将上下文目录(和子目录)发送到docker后台程序。

  • 如果<src>是目录,则复制目录的全部内容,包括文件系统元数据。

**注意:**不复制目录本身,只复制其内容。

  • 如果<src>是任何其他类型的文件,则将其与元数据一起单独复制。如果<dest>以斜杠/结尾,则将其视为目录,<src>的内容将写入<dest>/base(<src>)

  • 如果直接或由于使用通配符指定了多个<src>资源,则<dest>必须是目录,并且必须以斜杠/结尾。

  • 如果<dest>不以斜杠结束,则将其视为常规文件,<src>的内容将写入<dest>

  • 如果<dest>不存在,则会在其路径中创建所有缺少的目录。

ENTRYPOINT

ENTRYPOINT有两种形式:

  • ENTRYPOINT ["executable", "param1", "param2"] (exec形式,推荐)
  • ENTRYPOINT command param1 param2 (shell 形式)

ENTRYPOINT允许您配置容器将执行的可执行文件。

例如,以下将使用其默认内容启动nginx,监听听端口80:

docker run -i -t --rm -p 80:80 nginx

docker run <image>的命令行参数将添加在ENTRYPOINT exec形式的所有项之后,并将覆盖使用CMD指定的项。 这允许将参数传递给入口点,即docker run <image> -d-d参数传递给入口点。您可以使用docker run --entrypoint标记覆盖ENTRYPOINT指令。

shell形式可以避免使用任何CMDrun命令行参数,但缺点是ENTRYPOINT将作为/ bin/sh -c的子命令启动,它不传递信号。 这意味着可执行文件不是容器的PID 1 ——并且不会收到Unix信号——因此您的可执行文件不会从docker stop <container>接收到SIGTERM

Dockerfile中只有最后一个ENTRYPOINT指令才会生效。

exec形式ENTRYPOINT示例

您可以使用ENTRYPOINT的exec形式设置稳定的默认命令和参数,然后使用任一形式的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

并且您可以优雅地请求使用docker stop test来关闭top

以下Dockerfile显示使用ENTRYPOINT在前台运行Apache(即,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"]

如果需要为单个可执行文件编写启动脚本,可以使用execgosu命令确保最终的可执行文件接收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 execdocker 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形式一样,是由shell进行环境变量解释,而不是docker。

Shell形式ENTRYPOINT示例

您可以为ENTRYPOINT指定一个纯字符串,它将在/bin/ sh -c中执行。 此形式将使用shell来处理shell环境变量替换,并将忽略任何CMDdocker 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

从上面的输出中可以看到,ENTRYPOINT不是PID 1

如果您随后运行docker stop test,则容器将无法完全退出——超时后,将被迫发送一个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

理解CMD和ENTRYPOINT如何相互作用

CMDENTRYPOINT指令均定义运行容器时执行的命令。 这是一些规则描述他们如何合作。

  1. Dockerfile应至少指定CMDENTRYPOINT命令之一。

  2. 使用可执行的容器时,应定义ENTRYPOINT

  3. 应将CMD用作为ENTRYPOINT命令定义默认参数或在容器中执行自定义命令的一种方式。

  4. 使用替代参数运行容器时,CMD将被覆盖。

下表显示了不同的ENTRYPOINT/CMD组合执行的命令: | | NO ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT ["exec_entry", "p1_entry"] | | :---- | :---- | :---- | :---- | | No CMD | error, not allowed | /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/logVOLUME /var/log /var/db。 有关通过Docker客户端使用的更多信息/示例和挂载指令,请参阅通过Volumes共享目录

docker run命令初始化新创建的卷,使用在基本镜像内指定位置上存在的任何数据。 例如,考虑以下Dockerfile片段:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

该Dockerfile生成一个镜像,该镜像使docker run/myvol创建一个新的挂载点并将greeting文件复制到新创建的卷中。

指定卷的注意事项

关于Dockerfile中的卷,请记住以下几点。

  • **基于Windows的容器上的卷:**使用基于Windows的容器时,容器内的卷的目的地必须是以下之一:

    • 不存在的目录或空目录
    • C:以外的驱动器
  • **在Dockerfile中更改卷:**在声明了卷后,如果有任何构建步骤更改了卷内的数据,则这些更改将被丢弃。

  • **JSON格式:**列表被解析为JSON数组。您必须用双引号(")而不是单引号(')括起单词。

  • **主机目录在容器运行时声明:**主机目录(挂载点)从本质上说是依赖于主机的。这是为了保持镜像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。因此,您无法从Dockerfile中挂载主机目录。 VOLUME指令不支持指定host-dir参数。创建或运行容器时,必须指定挂载点。

USER

USER <user>[:<group>] or
USER <UID>[:<GID>]

USER指令设置运行镜像时要使用的用户名(或UID)以及可选的用户组(或GID),Dockerfile中该指令之后的所有RUNCMDENTRYPOINT指令也以此用户运行。

**警告:**如果用户没有主要组(primary group),则该镜像(或后续说明)将与root组运行。

在Windows上,如果不是内置帐户,则必须首先创建用户。 这可以通过Dockerfile中调用的net user命令来完成。

    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指令为Dockerfile中跟在其后的所有RUNCMDENTRYPOINTCOPYADD指令设置工作目录。 如果WORKDIR不存在,即使以后的Dockerfile指令中未使用。

WORKDIR指令可在Dockerfile中多次使用。 如果指定的是相对路径,则它将相对于上一个WORKDIR指令的路径。 例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

Dockerfile中最后一个pwd命令的输出为/a/b/c

WORKDIR指令可以解析在它前使用ENV设置的环境变量。 您只能使用在Dockerfile中显式设置的环境变量。 例如:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

Dockerfile中最后一个pwd命令的输出为/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命令。

默认值

ARG指令可以可选的包含默认值:

FROM busybox
ARG user1=someuser