【翻译】Dockerfile参考

时间:2022-11-23 16:24:04

Dockerfile参考

来自docker官方网址:https://docs.docker.com/engine/reference/builder/

docker能够从Dockerfile中读取指令并自动构建一个镜像。Dockerfile是一个文本文档,它包含用户可以在命令行上调用的所有命令来组装一个镜像。使用docker构建,用户可以创建一个连续执行多个命令行指令的自动化构建。

这个页面描述了你可以在Dockerfile中使用的命令。读完此页后,请参阅Dockerfile最佳实践(Dockerfile Best Practices)以获得面向tip的指南。

前言

翻译这篇文章前我们先阐述一个重要的概念,那就是上下文(context):docker构建时会将上下文的所有内容(.dockerignore中指定的内容除外)发送到daemon中进行处理。那么如何指定这个上下文呢?我们看一个命令来更容易的理解:

docker build -t xxxxx .

注意这行命令后面有一个.(小点),这个小点的作用不是指定dockerfile的位置,指定dockerfile的位置是用-f参数来指定的,那你以为这个小点是干嘛的?他就是用来指定构建镜像的上下文的。

理解了以上的例子后,我们再看看COPY这个命令的真是含义:

COPY ./package.json /app/

COPY是将源目标复制到镜像中的一个命令,那么上面这个./package.json是指什么路径呢?这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json

用法描述

docker构建命令(docker build)从Dockerfile和上下文构建一个映像。构建的上下文是位于指定位置路径或URL的文件集。路径是本地文件系统上的一个目录。URL是一个Git存储库位置。

上下文会被递归的处理。因此,路径包含任何子目录,URL包含存储库及其子模块。这个例子显示了一个使用当前目录作为上下文的构建命令:

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

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

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

要在构建上下文中使用文件,Dockerfile引用一条指令中指定的文件,例如一条COPY指令。要提高构建的性能,可以通过向上下文目录添加.dockerignore文件来排除文件和目录。有关如何创建.dockerignore文件的信息,请参阅此页上的文档。 create a .dockerignore file

通常,Dockerfile的文件名称默认就是Dockerfile(首字母D大写),位于上下文的根目录中。在docker build中使用-f标志指向文件系统中任何位置的Dockerfile。

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

您可以指定一个存储库(repository)和用于保存新映像的标记(tag),这会在镜像构建成功之后应用:

 docker build -t shykes/myapp .

要在构建之后将映像标记到多个存储库中,请在运行构建命令时添加多个-t参数:

docker build -t shykes/myapp:1.0. -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守护进程将自动清理您发送的上下文。

注意,每条指令都是独立运行的,并且会创建一个新镜像——因此,运行cd /tmp不会对接下来的指令产生任何影响。

只要有可能,Docker将重用中间镜像(缓存),以显著加快Docker的构建过程。这由控制台输出中的Using cache消息表示。(有关更多信息,请参阅Dockerfile最佳实践指南中的Build cache部分 Build cache section):

$ docker build -t svendowideit/ambassador .
Sending build context to Docker daemon 15.36 kB
Step / : FROM alpine:3.2
---> 31f630c65071
Step / : MAINTAINER SvenDowideit@home.org.au
---> Using cache
---> 2a1c91448f5f
Step / : RUN apk update && apk add socat && rm -r /var/cache/
---> Using cache
---> 21ed6e7fbb73
Step / : 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

构建缓存仅用于具有本地父链(parent chain)的映像。这意味着这些映像是由以前的构建创建的,或者整个映像链是用docker加载的。如果希望使用特定映像的构建缓存,可以使用--cache-from选项指定它。使用--cache-from指定的镜像不需要父链,可以从其他仓库(registries)获取。

完成构建之后,就可以考虑浏览Pushing a repository to its registry

构建工具BuildKit

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

  • 检测并跳过未使用的构建阶段(stage)

  • 并行化构建独立的构建阶段

  • 在构建之间只会增量地传输构建上下文中更改的文件

  • 检测并跳过在构建上下文中传输未使用的文件

  • 使用带有许多新特性的外部Dockerfile实现

  • 避免API其余部分的副作用(中间镜像和容器)

  • 为自动修剪(prune)设置构建缓存的优先级

要使用BuildKit后端,您需要在调用docker构建之前在CLI上设置一个环境变量DOCKER_BUILDKIT=1。

要了解基于BuildKit的构建可用的实验性Dockerfile语法,请参考BuildKit存储库中的文档: refer to the documentation in the BuildKit repository.

格式

下面是Dockerfile的格式:

# 注释
指令 参数

指令不区分大小写。但是,习惯上它们是大写的,以便更容易地将它们与参数区分开。

Docker按顺序在Dockerfile中运行指令。Dockerfile必须以“FROM”指令开头。它可能会在解析器指令、注释和全局作用域的参数之后。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
  • 支持以下解析器指令:
  1. syntax
  2. escape

syntax

格式:

# syntax=[remote image reference]
# syntax=docker/dockerfile
# syntax=docker/dockerfile:1.0
# syntax=docker.io/docker/dockerfile:
# syntax=docker/dockerfile:1.0.-experimental
# syntax=example.com/user/repo:tag@sha256:abcdef...

只有在使用BuildKit后端时该指令才有用。

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

自定义Dockerfile的实现允许你:

  • 在不用更新后台守护程序的情况下自动的获取错误修正
  • 确保所有用户使用相同的实现来构建Dockerfile
  • 在不升级后台守护程序的情况下获取最新的功能
  • 尝试新的实验性或第三方特性

官方的版本

escape转义符

environment replacement环境的替换

.dockerignore文件

.dockerignore文件的主旨是要“排除”一些发网daemon的文件,但是里面也有一些逻辑是不排除的,你要先明确这一点。

在docker CLI将上下文发送到docker守护进程之前,它在上下文的根目录中查找一个名为.dockerignore的文件。如果该文件存在,CLI将修改上下文以排除与其中模式匹配的文件和目录。这有助于避免不必要地向守护进程发送大型或敏感的文件和目录,并可能使用ADD或COPY将它们添加到映像中。

CLI将.dockerignore文件解释为一个新行分隔的模式列表,类似于Unix shell的文件globs。为了进行匹配,上下文的根被认为是工作目录和根目录。例如,模式/foo/bar和foo/bar都排除了路径的foo子目录或位于URL的git存储库根目录中名为bar的文件或目录。两者都不排斥其他任何东西。

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

# 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方法消除.和. .。预处理后为空的行将被忽略。

依赖于filepath.Match提供的规则,Docker使用一个特殊的通配符**来匹配任意数量的目录,比如:**/*.go这个会匹配所有目录中.go扩展名的文件,包括构建的根目录。

以!(感叹号)开始的行可用于排除的例外情况。下面是一个例子,.dockerignore文件使用这个机制:

  *.md
!README.md

除了README.md外所有的markdown文件都被排除在上下文外了。

放置!的地方影响如下行为:.dockerignore中与特定文件匹配的最后一行决定它是被包含还是被排除。考虑下面的例子:

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

首先所有的markdown文件都被排除了。

在!README*.md下面还有一行 README-secret.md那么结果就是除了这个README-secret.md的所有README开头的markdown文件不会被排除。

在来看下面这个例子:

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

首先所有的markdown文件都被排除了

然后第二行README-secret.md这个可以不写,因为第一行已经指定了规则,它也符合第一行的规则。

第三行又将所有README开头的markdown文件包含了进来,也就是说第二行根本不会起作用了,它指定的这个文件因为第三行的规则会被包括进去。

甚至可以使用.dockerignore文件来排除Dockerfile和.dockerignore文件。这些文件仍然被发送到守护进程,因为守护进程需要它们来完成自己的工作。但是ADD和COPY指令不会将它们复制到镜像。

最后,你可能希望指定要在上下文中包含哪些文件,而不是要排除哪些文件。要实现这一点,将*指定为第一个模式,然后是一个或多个模式!例外模式。

note:作为历史原因你应该了解,.模式被忽略了。

FROM

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

FROM指令初始化一个新的构建阶段,并为后续指令设置基本镜像。因此,一个有效的Dockerfile必须以FROM指令开始。镜像可以是任何有效的镜像—从公共存储库(docker hub)中提取镜像尤其容易。

  • ARG是Dockerfile中唯一可能在前面的指令。了解ARG和FROM如何交互: Understand how ARG and FROM interact.。
  • FROM可以在一个Dockerfile中出现多次,用来创建多个镜像或者使用一个构建阶段当作另外一个构建阶段的依赖项。只需在每条新的FROM指令之前,通过提交来记录最后一个镜像ID的输出。每个FROM指令清除以前的指令创建的任何状态。
  • 可以通过将 AS name添加到FROM指令中,就是给这个构建阶段指定了一个名称,当然这是可选的。该名称可用于后续的FROM和COPY --from=<name|index>指令,在此引用此阶段构建的镜像
  • tag或digest的值是可选的。如果省略其中任何一个,构建器默认使用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之后的任何指令中都不能使用它。若要使用在第一个之前声明的ARG的默认值,请使用在构建阶段内没有值的ARG指令:

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

RUN

RUN有两种格式:

#shellform,命令在shell中运行,默认是Linux上的/bin/sh -c或Windows上的cmd /S /C
RUN <command>
#execform
RUN ["executable", "param1", "param2"]

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

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

execform格式的RUN指令在避免shell字符串转换和在一个不包含指定的可执行shell的基础镜像上运行RUN指令成为可能。

shellform格式下的默认shell可以通过SHELL命令来修改。

在shellform的格式下,可以使用\(反斜杠)将单个运行指令延续到下一行。例如,考虑这两行:

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

与下面这一行是等同的:

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

note:使用exec form格式时,可以将目标shell传入,这可以使你使用一个不同的shell,而不是‘/bin/sh’。如下例:

RUN ["/bin/bash", "-c", "echo hello"]

note:exec from格式命令会被解析成json数组,这意味着你必须使用双引号而不是单引号来括住数组中的每一个元素。

与shellform不同,execform不调用命令shell。这意味着不会发生正常的shell处理。例如,RUN ["echo", "$HOME"]不会对$HOME执行变量替换。如果想要shell处理,那么要么使用shellform,要么直接执行shell,例如:RUN ["sh", "-c", "echo $HOME"]。当使用execform并直接执行shell时(如shellform的情况),执行环境变量扩展的是shell,而不是docker。

在JSON格式中(execform会被当作json处理,还记得吗),必须转义反斜杠。这在以反斜杠为路径分隔符的windows系统特别相关。下面这行代码由于不是有效的JSON,将被视为shellform进行处理,并以一种意外的方式失败:运行["c:\windows\system32\tasklist。本例的正确语法是:RUN ["c:\\windows\\system32\\tasklist.exe"]

RUN指令的缓存不会在下一次构建期间自动失效。像RUN apt-get distt -upgrade -y这样的指令的缓存将在下一个构建过程中重用。RUN指令的缓存可以通过使用--no-cache标志来失效,例如docker build --no-cache。

从 Dockerfile Best Practices guide 查看更多信息。

运行指令的缓存可以通过添加指令失效。详见下文below

关于RUN指令已提交的ISSUE

Issue 783 is about file permissions problems that can occur when using the AUFS file system. You might notice it during an attempt to rm a file, for example.

For systems that have recent aufs version (i.e., dirperm1 mount option can be set), docker will attempt to fix the issue automatically by mounting the layers with dirperm1 option. More details on dirperm1 option can be found at aufs man page

If your system doesn’t have support for dirperm1, the issue describes a workaround.

CMD

CMD有三种格式:

#exec form, 推荐格式
CMD ["executable","param1","param2"]
#作为ENTRYPOINT的默认参数
CMD ["param1","param2"]
#shell form
CMD command param1 param2 (shell form)

Dockerfile中只能存在一个CMD,如果你有多个CMD,那么最后那个CMD命令会生效。

CMD的主要用途是为执行容器提供缺省值。这些缺省值可以包括可执行文件,也可以省略可执行文件,在这种情况下,您还必须指定一个ENTRYPOINT指令。

NOTE:如果CMD用于为ENTRYPOINT指令提供默认参数,那么应该使用JSON数组格式指定CMD和ENTRYPOINT指令。

NOTE:exec form格式的会被当作JSON数组来对待,这意味着你必须在数组里面使用双引号而不是单引号。

NOTE:exec form不会像shell form那样会调用shell命令,这意味着不会发生普通的shell处理,比如CMD [ "echo", "$HOME" ]这条命令不会在$HOME上发生变量替换。如果想要shell处理,那么要么使用shell form,要么直接执行shell,比如:CMD [ "sh", "-c", "echo $HOME" ]。当使用exec form并直接执行shell时(如shell form的情况),执行环境变量扩展的是shell,而不是docker。

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

如果你使用的是shell form的格式,CMD会在/bin/sh -c里执行:

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

如果不想使用shell来运行命令,那么必须将该命令表示为JSON数组,并给出可执行文件的完整路径。这种数组形式是CMD的推荐格式。任何附加参数必须单独表示为数组中的字符串:

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

如果希望容器每次都运行相同的可执行文件,那么应该考虑将ENTRYPOINT与CMD结合使用。查看这里: ENTRYPOINT

如果用户指定docker运行的参数,那么他们将覆盖CMD中指定的默认值。

不要将RUN与CMD混淆。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指令中指定多个标签:

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

FROM行中指定的基础镜像或父镜像中的LABEL会被你的镜像继承,如果一个LABEL已经存在但是有不同的值,那么最近使用的LABEL的值会覆盖之前声明的那个值。

使用docker inspect命令来查看LABEL:

"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 (已废弃)

LABEL指令是这个指令的替代。

不描述这个指令了,不推荐用,如果你有新项目,那么使用LABEL来代替它,没有必要在使用这个指令。

EXPOSE

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

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

EXPOSE指令实际上并不发布端口。它的功能类似于构建镜像的人员和运行容器的人员之间的一种文档类型,关于要发布哪些端口。也就是说这个命令就是一个提示作用。要在运行容器时实际发布端口,可以使用docker run上的-p标志来发布和映射一个或多个端口,或者使用-p标志来发布所有公开的端口并将它们映射到高阶端口。

默认情况下EXPOST假设使用的TCP协议,你可以指定其他协议:

EXPOSE /udp

要在相同端口上暴露两个端口,可以指定两行:

EXPOSE /tcp
EXPOSE /udp

在这种情况下,如果在docker run中使用-P,那么TCP和UDP端口将分别公开一次。请记住,-P在主机上使用临时的高阶主机端口,因此TCP和UDP的端口将不相同。

无论EXPOSE做了怎么样的设置,你都可以在运行镜像时通过-p标志来覆盖他们:

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

要在主机系统上设置端口重定向,请参阅 using the -P flag.。docker network命令支持在容器之间创建通信网络,而不需要公开或发布特定的端口,因为连接到docker network的容器可以通过任何端口相互通信。有关详细信息,请参阅 overview of this feature

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>更改它们。

NOTE:环境持久性可能会导致意想不到的副作用。例如,设置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>"]

NOTE--chown特性只支持用于构建Linux容器的Dockerfiles,不能用于Windows容器。由于用户和组所有权概念不能在Linux和Windows之间转换,因此使用/etc/passwd和/etc/group将用户名和组名转换为IDs,这限制了该特性只能用于基于Linux os的容器。

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

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

每一个<src>可以指定通配符,这个通配符会被GO语言的 filepath.Match 提供的逻辑进行处理。比如:

ADD hom* /mydir/        # 添加所有以"hom"开头的文件
ADD hom?.txt /mydir/ # ? 会被一个单独的字符代替, 比如, "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[[]].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/

所有新创建的文件和目录的UID和GID都是0,除非可选的--chown标志指定了一个给定的用户名、groupname或UID/GID组合来请求添加内容的特定所有权--chown标志的格式允许在任何组合中使用用户名和groupname字符串或直接整数UID和GID。提供没有groupname的用户名或没有GID的UID将使用与GID相同的数字UID。如果提供了用户名或groupname,则将使用容器的根文件系统/etc/passwd和/etc/group文件分别执行从名称到整数UID或GID的转换。下面的例子展示了--chown标志的有效定义:

ADD --chown=:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown= files* /somedir/
ADD --chown=: files* /somedir/

如果容器根文件系统不包含/etc/passwd或/etc/group文件,并且在--chown标志中使用了用户名或组名,那么在添加操作时构建将失败。使用数字id不需要查找,也不依赖于容器根文件系统的内容。

在<src>是远程文件URL的情况下,目的地的权限为600。如果正在检索的远程文件具有HTTP Last-Modified标头,则来自该标头的时间戳将用于设置目标文件的时间。但是,与添加过程中处理的任何其他文件一样,在确定文件是否更改以及是否应该更新缓存时,并不包括mtime。

NOTE:如果通过STDIN (docker build - < somefile)传递Dockerfile进行构建,则没有构建上下文,因此Dockerfile只能包含基于URL的ADD指令。您还可以通过STDIN (docker build - < archive.tar.gz)传递压缩的归档文件,归档文件根目录下的Dockerfile和归档文件的其余部分将用作构建的上下文。注:STDIN是标准输入的意思。

NOTE:如果你的URL文件使用身份验证进行保护,那么您将需要在容器中使用RUN wget、RUN curl或其他工具,因为ADD指令不支持身份验证。

NOTE:如果<src>的内容发生了变化,那么第一个遇到的ADD指令将使Dockerfile中的所有后续指令的缓存无效。这包括使RUN指令的缓存无效。有关更多信息,请参阅Dockerfile最佳实践指南:Dockerfile Best Practices guide 。

ADD指令遵循下列规则:

  • <src>路径必须在构建的上下文中;你不能ADD一个 ../something/something/,因为docker构建的第一步是将上下文目录(和子目录)发送到docker守护进程而不是在本地执行。
  • 如果<src>是一个URL,并且<dest>没有以斜杠结尾,则从该URL下载一个文件并复制到<dest>。
  • 如果<src>是一个URL,并且<dest>以一个斜杠结尾,那么从URL推断文件名,文件被下载到<dest>/<filename>。例如,ADD http://example.com/foobar  /(注意空格)将创建文件/foobar。URL必须有一个重要的路径,这样才能在这种情况下找到一个合适的文件名(http://example.com将不起作用)。
  • 如果<src>是一个目录,则复制目录的全部内容,包括文件系统元数据。

NOTE:目录本身不是复制的,只是它的内容。

  • 如果<src>是一个以可识别的压缩格式(identity、gzip、bzip2或xz)进行压缩的本地tar存档,则将其作为目录解压缩。来自远程url的资源没有解压缩。当一个目录被复制或解压缩时,它的行为与tar -x相同,结果是:
  1. 无论目标路径上存在什么

  2. The contents of the source tree, with conflicts resolved in favor of “2.” on a file-by-file basis.(不知道什么狗屁意思,以后再来处理吧)
  • NOTE:一个文件是否被识别为一种可识别的压缩格式完全取决于文件的内容,而不是文件名。例如,如果一个空文件以.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>"]

COPY指令基本与ADD指令差不多,差别就在ADD指令会在识别到压缩文件后对文件进行解压缩,COPY不会。COPY的内容就不翻译了,以后有变化再说。

ENTRYPOINT

ENTRYPOINT有两种格式:

#(exec form, 推荐的格式)
ENTRYPOINT ["executable", "param1", "param2"]
#(shell form)
ENTRYPOINT command param1 param2

ENTRYPOINT允许你配置一个可当作可执行程序运行的容器。

例如,下面的命令启动一个nginx容器,使用默认的内容,并且监听80端口:

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

docker run <image>的命令行参数将追加到以exec form格式执行的ENTRYPOINT指令中的所有元素之后,并将覆盖使用CMD指定的所有元素。这允许参数被传递到入口点,也就是说,docker run <image> -d将把-d参数传递给入口点。你可以使用docker run - ENTRYPOINT标志覆盖ENTRYPOINT指令。

shell form格式的可以防止使用任何CMD或run命令行参数,但是有一个缺点,即你的ENTRYPOINT 将作为/bin/sh -c的子命令启动,该命令不传递信号。这意味着可执行文件不是容器的PID 1,也不会收到Unix信号,所以你的可执行文件不会收到来自docker stop <container>的终止信号。

只有最后的ENTRYPOINT 会生效。

Exec form格式的ENTRYPOINT 例子

您可以使用ENTRYPOINT的exec form格式来设置相当稳定的默认命令和参数,然后使用CMD的任何一种形式来设置更可能更改的其他默认值。

FROM ubuntu
ENTRYPOINT ["top", "-b"]#相对稳定的命令和参数
CMD ["-c"]#可能会更改的参数

当你是用上面的Dockfile运行容器,你可以看到top是唯一的进程:

$ docker run -it --rm --name test  top -H
top - :: up :, users, load average: 0.00, 0.01, 0.05
Threads: total, running, sleeping, stopped, 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: total, used, free, buffers
KiB Swap: total, used, free. cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
root R 0.0 0.1 :00.04 top

要进一步检查结果,可以使用docker exec:

$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 2.6 0.1 ? Ss+ : : top -b -H
root 0.0 0.1 ? R+ : : ps aux

你可以使用docker stop测试优雅地请求top关闭。

下面的Dockerfile展示了如何使用ENTRYPOINT在前台运行Apache(即, 作为 PID 1):

FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE
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 0.1 0.0 ? Ss+ : : /bin/sh /run.sh cmd cmd2
root 0.0 0.2 ? Ss : : /usr/sbin/apache2 -k start
www-data 0.2 0.2 ? Sl : : /usr/sbin/apache2 -k start
www-data 0.2 0.2 ? Sl : : /usr/sbin/apache2 -k start
root 0.0 0.1 ? R+ : : ps aux
$ docker top test
PID USER COMMAND
root {run.sh} /bin/sh /run.sh cmd cmd2
root /usr/sbin/apache2 -k start
/usr/sbin/apache2 -k start
/usr/sbin/apache2 -k start
$ /usr/bin/time docker stop test
test
real 0m .27s
user 0m .03s
sys 0m .03s

NOTE:您可以使用--ENTRYPOINT覆盖ENTRYPOINT设置,但这只能将二进制文件设置为exec(不使用sh -c)。

NOTE:exec form的格式的命令会被解析为json数组,意味着你必须将数组的元素用双引号括住而不是单引号。

NOTE:与shell form不同,exec form不调用命令shell。这意味着不会发生正常的shell处理。例如,ENTRYPOINT ["echo", "$HOME"]不会对$HOME执行变量替换。如果您想要shell处理,那么要么使用shell form,要么直接执行shell,例如:ENTRYPOINT ["sh", "-c", "echo $HOME"]。当使用exec form并直接执行shell时(如shell form的情况),执行环境变量扩展的是shell,而不是docker。

Shell格式的ENTRYPOINT的例子

以后再说

理解CMD和ENTRYPOINT是如何交互的

CMD和ENTRYPOINT指令都定义了在运行容器时执行什么命令。这里有一些规则描述他们的协作关系。

  1. dockerfile应该至少指定一个CMD或者ENTRYPOINT指令
  2. 在将容器用作可执行文件时,应该定义ENTRYPOINT。
  3. CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行特别命令的方法。
  4. 当运行带有可选参数的容器时,CMD将被覆盖。

下表显示了对不同的ENTRYPOINT/ CMD组合执行的命令:

  无ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“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

NOTE:如果CMD是从基础镜像定义的,那么设置ENTRYPOINT将把CMD重置为空值。在这种情况下,必须在当前镜像中定义CMD才能获得一个值。

VOLUME

VOLUME ["/data"]

VOLUME指令使用指定的名称创建一个挂载点,并将其标记为持有来自本机主机或其他容器的外部挂载卷。该值可以是一个JSON数组,VOLUME ["/var/log/"],也可以是一个有多个参数的普通字符串,比如VOLUME /var/log或VOLUME /var/log/ var/db。有关通过Docker客户端的更多信息/示例和安装说明,请参阅通过卷文档共享目录: Share Directories via Volumes.。

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

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

这个Dockerfile会产生一个镜像,这个镜像会导致docker运行时在/myvol处创建一个新的挂载点,并将greeting文件复制到新创建的卷中。

关于指定VOLUME的说明

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

  • 基于windows的容器上的卷:当使用基于windows的容器时,容器内的卷的目标必须是:
    • 不存在的或空目录
    • 一个C盘以外的驱动器
  • 从Dockerfile中更改卷:如果任何构建步骤在声明卷之后更改了其中的数据,那么这些更改将被丢弃。
  • JSON格式:该列表被解析为JSON数组。必须用双引号(")而不是单引号(')括起单词。
  • 主机目录在容器运行时声明:主机目录(挂载点)本质上依赖于主机。这是为了保持镜像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。由于这个原因,您不能从Dockerfile中挂载主机目录。VOLUME指令不支持指定主机目录参数。在创建或运行容器时,必须指定挂载点。

USER

后续翻译

WORKDIR

WORKDIR /path/to/workdir

WORKDIR指令为Dockerfile中的任何RUN、CMD、ENTRYPOINT、COPY和ADD指令设置工作目录。如果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