Best practices for writing Dockerfiles

Estimated reading time: 24 minutes
Docker can build images automatically by reading the instructions from a Dockerfile, a text file that contains all the commands, in order, needed to build a given image. Dockerfiles adhere to a specific format and use a specific set of instructions. You can learn the basics on the Dockerfile Reference page. If you’re new to writing Dockerfiles, you should start there.

Docker 可以通过从 Dockerfile 中读取指令来自动构建镜像,Dockerfile 是一个包含构建给定镜像所需的所有命令的文本文件。Dockerfiles 遵循特定的格式,并使用一组特定的指令。您可以在 Dockerfile 参考页面上学习基础知识。如果你是写 Dockerfiles 的新手,你应该从那里开始。

This document covers the best practices and methods recommended by Docker, Inc. and the Docker community for building efficient images. To see many of these practices and recommendations in action, check out the Dockerfile for buildpack-deps.

本文档涵盖Docker,inc.和 Docker 社区为构建高效镜像而推荐的最佳实践和方法。要查看许多这些实践和建议的实施情况,请查看 build pack-deps 的 Dockerfile。

Note: for more detailed explanations of any of the Dockerfile commands mentioned here, visit the Dockerfile Reference page. 注意,要查看本章提及到的Dockerfile命令,请访问Dockerfile Reference页面。

General guidelines and recommendations

通用准则和推荐

Containers should be ephemeral

容器本该是短暂的

The container produced by the image your Dockerfile defines should be as ephemeral as possible. By “ephemeral,” we mean that it can be stopped and destroyed and a new one built and put in place with an absolute minimum of set-up and configuration. You may want to take a look at the Processes section of the 12 Factor app methodology to get a feel for the motivations of running containers in such a stateless fashion.

Dockerfile 定义的镜像生成的容器应该尽可能短暂。我们所说的 “短暂” 是指它可以被随时停止和销毁,一个新的容器可以在绝对最少的设置和配置下构建和实施。您可能想看看 12 因子应用程序方法的流程(Processes)部分,以了解以这种无状态方式运行容器的动机。

Use a .dockerignore file 使用.dockerignore文件

The current working directory where you are located when you issue a docker build command is called the build context, and the Dockerfile must be somewhere within this build context. By default, it is assumed to be in the current directory, but you can specify a different location by using the -f flag.

“构建上下文(build context)”是您当前执行docker build命令的工作目录,并且Dockerfile必须在此上下文的某个路径。默认情况,docker假设他在当前目录,但您可使用-f标签指定一个另外的位置。

Regardless of where the Dockerfile actually lives, all of the recursive contents of files and directories in the current directory are sent to the Docker daemon as the build context. Inadvertently including files that are not necessary for building the image results in a larger build context and larger image size. These in turn can increase build time, time to pull and push the image, and the runtime size of containers. To see how big your build context is, look for a message like the following, when you build your Dockerfile.

不管Dockerfile的位置在哪里,当前目录的所有目录的子目录、子文件都被发送到Docker daemon,作为build context(构建上下文)。无意包括了不必须的文件将导致更大的构建上下文(build context)和更大的镜像大小。这些反过来增加构建时间,拉取和推送镜像的时间以及容器的运行时大小。如果想查看构建上下文的大小,请在构建Dockerfile的日志中搜索一下内容:

  1. Sending build context to Docker daemon 187.8MB

To exclude files which are not relevant to the build, without restructuring your source repository, use a .dockerignore file. This file supports exclusion patterns similar to .gitignore files. For information on creating one, see the .dockerignore file. In addition to using a .dockerignore file, check out the information below on multi-stage builds.

如果想要排除与构建无关的文件,在不重构源代码仓库的情况下,请使用.dockerignore文件。此文件支持排除模板,此模板类似于.gitignore文件。想要了解更多,请查看.dockerignore文件指引。除了使用.dockerignore文件,请查阅后面的多阶段构建。

Use multi-stage builds 使用多阶段构建

If you use Docker 17.05 or higher, you can use multi-stage builds to drastically reduce the size of your final image, without the need to jump through hoops to reduce the number of intermediate layers or remove intermediate files during the build.

如果你使用17.05或更高版本的docker,你可使用多阶段构建来大幅度减少最终镜像的大小,而无须通过跳过hoops来减少中间层(layers)的数量或在构建过程中移除中间文件。

Images being built by the final stage only, you can most of the time benefit both the build cache and minimize images layers.

除了最后阶段构建的镜像,你大部分时间可在构建缓存和最少化镜像层中受益。

Your build stage may contain several layers, ordered from the less frequently changed to the more frequently changed for example:

您的构建阶段可能包括几层,按照更少变更到更频繁变更的顺序,举个例子:

  • Install tools you need to build your application

安装需用用于构建应用的工具;

  • Install or update library dependencies

安装或更新库依赖;

  • Generate your application

生成您的应用;

A Dockerfile for a go application could look like:
go应用的Dockerfile可以长这样:

FROM golang:1.9.2-alpine3.6 AS build
# Install tools required to build the project
# 安装构建项目需要的工具
# We will need to run `docker build --no-cache .` to update those dependencies
# 我们将需要运行'docker build --no-cache .'来更新这些依赖
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
# Gopkg.toml and Gopkg.lock lists project dependencies
# Gopkg.toml和Gopkg.lock列出项目的依赖
# These layers will only be re-built when Gopkg files are updated
#这些层仅在Gopkg文件更新的时候才会重新构建
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies 安装库依赖
RUN dep ensure -vendor-only
# Copy all project and build it 拷贝所有项目和构建它
# This layer will be rebuilt when ever a file has changed in the project directory
# 此层仅在项目任意文件有更新时才重建(rebuilt)
COPY . /go/src/project/
RUN go build -o /bin/project
# This results in a single layer image
# 以上形成单个镜像层
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

Avoid installing unnecessary packages

请避免安装不必要的packages

In order to reduce complexity, dependencies, file sizes, and build times, you should avoid installing extra or unnecessary packages just because they might be “nice to have.” For example, you don’t need to include a text editor in a database image.

为了减少复杂度、依赖、文件大小和构建时间,你应该避免安装额外或不必要的package,如果仅仅是因为他们可能是“最好是有”。例如,你无需再数据库镜像中包含一个文本编辑器。

Each container should have only one concern

每个容器理应仅有一个concern

Decoupling applications into multiple containers makes it much easier to scale horizontally and reuse containers. For instance, a web application stack might consist of three separate containers, each with its own unique image, to manage the web application, database, and an in-memory cache in a decoupled manner.

解耦应用到多个容器中让它可更好的水平扩展和服用容器。举个例子,一个web应用栈可能包括3个独立的容器,每个容器都有它自己的唯一的镜像,来管理web应用、数据库和内存缓存,以解耦的方式。

You may have heard that there should be “one process per container”. While this mantra has good intentions, it is not necessarily true that there should be only one operating system process per container. In addition to the fact that containers can now be spawned with an init process, some programs might spawn additional processes of their own accord. For instance, Celery can spawn multiple worker processes, or Apache might create a process per request. While “one process per container” is frequently a good rule of thumb, it is not a hard and fast rule. Use your best judgment to keep containers as clean and modular as possible.

你可能已经听说过“一个进程一个容器”的说法。尽管他的初衷是好的,但它不必恃真的,以便每个容器只有一个操作系统进程。容器现在可以使用一个init进程产生,除此之外,一些程序可产生额外的进程,根据他们自己的协议。例如,Celery可产生多个工作进程,或者Apache可创建一个进程/每个请求。尽管一个容器一个进程通常是一个很好的规则,但他不是硬核且快的规则。请使用您心中的度量来让容器尽可能的干净、模块化。

If containers depend on each other, you can use Docker container networks to ensure that these containers can communicate.

如果容器是相互依赖的,你可使用Docker容器网络来确保这些容器可相互通信。

Minimize the number of layers

最小化镜像层的数量

Prior to Docker 17.05, and even more, prior to Docker 1.10, it was important to minimize the number of layers in your image. The following improvements have mitigated this need:

在Docker 17.05甚至更早的版本,例如Docker 1.10,最小化您的镜像层数据是很重要的。以下的改进已经减轻了这一需求:

  • In Docker 1.10 and higher, only RUN, COPY, and ADD instructions create layers. Other instructions create temporary intermediate images, and no longer directly increase the size of the build.

在Docker 1.10或更高的版本,只有RUN,COPY和ADD指令可以创建镜像层。其他的指令创建的是临时中间层,并且不再增加该镜像大小。

  • Docker 17.05 and higher add support for multi-stage builds, which allow you to copy only the artifacts you need into the final image. This allows you to include tools and debug information in your intermediate build stages without increasing the size of the final image.

在Docker 17.05或更高的版本添加了多节点构建的支持,此功能允许你拷贝那些最终镜像所有的构建。这允许你的中间构建阶段包括工具和调试信息,而无需增加最终镜像的大小。

Sort multi-line arguments

请排序多行参数

Whenever possible, ease later changes by sorting multi-line arguments alphanumerically. This will help you avoid duplication of packages and make the list much easier to update. This also makes PRs a lot easier to read and review. Adding a space before a backslash (\) helps as well.

尽可能通过字母数字排序多行参数来简化以后的更改。这将帮助您避免包的重复,并使列表更容易更新。这也使得 PRs 更容易阅读和审查。在反斜杠 () 之前添加空格也有帮助。
Here’s an example from the [buildpack-deps](https://github.com/docker-library/buildpack-deps) image:
以下是buildpack-deps的镜像例子:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion

Build cache

构建缓存

During the process of building an image Docker will step through the instructions in your Dockerfileexecuting each in the order specified. As each instruction is examined Docker will look for an existing image in its cache that it can reuse, rather than creating a new (duplicate) image. If you do not want to use the cache at all you can use the --no-cache=true option on the docker build command.

构建镜像的过程,Docker将按照顺序执行Dockerfile中的每条指令。因为要确保每条指令被检查,Docker将在它的缓存中搜索已存在的镜像以便可以复用,而不是创建一个新的重复镜像。如果你压根不希望使用缓存,你可在运行docker build命令中传递—no-cache=true。

However, if you do let Docker use its cache then it is very important to understand when it will, and will not, find a matching image. The basic rules that Docker will follow are outlined below:

然而的是,如果你让Docker使用它的缓存,那么理解Docker什么时候会搜索匹配镜像以及什么时候不对将很重要。基础的规则是,Docker将遵循以下描述:

  • Starting with a parent image that is already in the cache, the next instruction is compared against all child images derived from that base image to see if one of them was built using the exact same instruction. If not, the cache is invalidated.

从已存在缓存的父镜像开始,下一个指令将于所有剥离基础镜像后的子镜像进行对比,以查看是否存在子镜像使用的指令与当前指令一模一样。如果不存在,该构建缓存无用。

  • In most cases simply comparing the instruction in the Dockerfile with one of the child images is sufficient. However, certain instructions require a little more examination and explanation.

在大部分情况下,简单比对Dockerfile中的指令与该子镜像的指令就只够。然而,有些指令要求一些更多的检查和解释。

  • For the ADD and COPY instructions, the contents of the file(s) in the image are examined and a checksum is calculated for each file. The last-modified and last-accessed times of the file(s) are not considered in these checksums. During the cache lookup, the checksum is compared against the checksum in the existing images. If anything has changed in the file(s), such as the contents and metadata, then the cache is invalidated.

对于ADD和COPY指令,Docker将检查镜像中文件的内容和计算文件的校验和。校验和不考虑文件的最后修改时间和访问时间。在搜索缓存的过程中,检验和将于已存在镜像中文件的校验和进行比较。如果文件有任何的修改,诸如内容、元数据,那么该缓存是无效的。

  • Aside from the ADD and COPY commands, cache checking will not look at the files in the container to determine a cache match. For example, when processing a RUN apt-get -y update command the files updated in the container will not be examined to determine if a cache hit exists. In that case just the command string itself will be used to find a match.

除了ADD和COPY命令,缓存检查将不会搜索容器的文件来决定缓存是否匹配。举个例子,当处理一个RUN apt-get -y update命令导致容器中文件的更新,Docker不将此作为决定缓存是否匹配的因素。在那种情况,只会匹配命令字符串本身来决定镜像是否匹配。

Once the cache is invalidated, all subsequent Dockerfile commands will generate new images and the cache will not be used.

一旦缓存无效,Dockerfile的所有后续命令将产生新的镜像,而该缓存将不使用。

The Dockerfile instructions

Dockerfile指令

Below you’ll find recommendations for the best way to write the various instructions available for use in a Dockerfile.
以下章节,你将找到编写不同Dockerfile指令的最佳实践。

FROM

Dockerfile reference for the FROM instruction
Whenever possible, use current Official Repositories as the basis for your image. We recommend the Alpine image since it’s very tightly controlled and kept minimal (currently under 5 mb), while still being a full distribution.
尽可能使用当前的官方存储库作为镜像的基础。我们推荐Alpine镜像,因为它的控制非常严格,并且保持最小 (目前在 5 mb 以下),同时仍然是一个完整的分布。

LABEL

Understanding object labels
You can add labels to your image to help organize images by project, record licensing information, to aid in automation, or for other reasons. For each label, add a line beginning with LABEL and with one or more key-value pairs. The following examples show the different acceptable formats. Explanatory comments are included inline.

您可以向镜像中添加标签,以帮助按项目组织镜像、记录许可信息、帮助自动化或出于其他原因。对于每个标签,添加一行以LABEL和一个或多个键值对开头。以下示例显示了不同的可接受格式。解释性评论包括在内。

Note: If your string contains spaces, it must be quoted or the spaces must be escaped. If your string contains inner quote characters ("), escape them as well. 注意,如果您的字符串包含空格,它必须在引号里边或者空格被转义。如果您的字符串包含了引号,请使用转义符转换它们。

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor="ACME Incorporated"
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

An image can have more than one label. Prior to Docker 1.10, it was recommended to combine all labels into a single LABEL instruction, to prevent extra layers from being created. This is no longer necessary, but combining labels is still supported.

镜像可以有多个label。在Docker 1.10之前的版本,推荐的做法是将所有labels组合到一个LABEL语句中以避免额外的镜像层产生。现在已经没有这个必要了,但组合label还是支持的。

# Set multiple labels on one line
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

The above can also be written as:
上面还可写作:

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
      com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"

See Understanding object labels for guidelines about acceptable label keys and values. For information about querying labels, refer to the items related to filtering in Managing labels on objects. See also LABEL in the Dockerfile reference.

有关可接受标签键和值的指南,请参见理解对象标签。有关查询标签的信息,请参考管理对象标签时与过滤相关的项目。另请参见 Dockerfile 参考中的标签。

RUN

Dockerfile reference for the RUN instruction
As always, to make your Dockerfile more readable, understandable, and maintainable, split long or complex RUN statements on multiple lines separated with backslashes.

一如既往,为了使 Dockerfile 更加可读、易懂和可维护,请用反斜杠\分隔的多行以拆分长或复杂的RUN语句。

APT-GET

Probably the most common use-case for RUN is an application of apt-get. The RUN apt-get command, because it installs packages, has several gotchas to look out for.

可能最常见的RUN用例是 apt-get 的应用程序。RUN apt-get 命令,因为它安装了软件包,有几个问题(gotchas)需要注意。
You should avoid RUN apt-get upgrade or dist-upgrade, as many of the “essential” packages from the parent images won’t upgrade inside an unprivileged container. If a package contained in the parent image is out-of-date, you should contact its maintainers. If you know there’s a particular package, foo, that needs to be updated, use apt-get install -y foo to update automatically.

Always combine RUN apt-get update with apt-get install in the same RUN statement. For example:
请总是组合RUN apt-get update和apt-get install一起使用,在同一RUN语句中。举个例子:

RUN apt-get update && apt-get install -y \
        package-bar \
        package-baz \
        package-foo

Using apt-get update alone in a RUN statement causes caching issues and subsequent apt-get installinstructions fail. For example, say you have a Dockerfile:

在RUN语句中运动单个的apt-get update将引起缓存问题,并且后续的apt-get install指令将失败。举个例子,假设你的Dockerfile是这样的:

FROM ubuntu:14.04
    RUN apt-get update
    RUN apt-get install -y curl

After building the image, all layers are in the Docker cache. Suppose you later modify apt-get install by adding extra package:

构建完该镜像后,所有的镜像层都存在Docker缓存中。假设后面你修改apt-get install来添加额外的包:

FROM ubuntu:14.04
    RUN apt-get update
    RUN apt-get install -y curl nginx

Docker sees the initial and modified instructions as identical and reuses the cache from previous steps. As a result the apt-get update is NOT executed because the build uses the cached version. Because the apt-get update is not run, your build can potentially get an outdated version of the curl and nginxpackages.

Docker认为最初和修改后的指令是相同的,并复用了上一个步骤的缓存。结果是apt-get update指令没有被执行,因为该构建使用了缓存的版本。由于apt-get update没有运行,您的构建可能使用了一个过时版本的curl和nginx包。

Using RUN apt-get update && apt-get install -y ensures your Dockerfile installs the latest package versions with no further coding or manual intervention. This technique is known as “cache busting”. You can also achieve cache-busting by specifying a package version. This is known as version pinning, for example:
使用确保您的Dockerfile安装的是最新版本的package,而无需额外的编码或手工介入。此技术被称为“缓存busting”。你还可通过指定版本号来达到“缓存busting”。这被称为version pinning,举个例子:

RUN apt-get update && apt-get install -y \
        package-bar \
        package-baz \
        package-foo=1.3.*

Version pinning forces the build to retrieve a particular version regardless of what’s in the cache. This technique can also reduce failures due to unanticipated changes in required packages.

Version pining强迫构建检索特定的版本,而不管缓存中的版本是什么。此技术还可减少由于特定package意外变更引起的失败。

Below is a well-formed RUN instruction that demonstrates all the apt-get recommendations.
下面是良好格式的RUN指令,演示了所有apt-get的推荐写法:

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

The s3cmd instructions specifies a version 1.1.*. If the image previously used an older version, specifying the new one causes a cache bust of apt-get update and ensure the installation of the new version. Listing packages on each line can also prevent mistakes in package duplication.

s3cmd指令指定了版本1.1.*。如果该镜像之前使用了一个较旧的版本,指定一个新的版本建该因此apt-get update的cache bust,并且能确保安装的是最新版本。在每行中列出package还可预防重复声明(package)错误。

In addition, when you clean up the apt cache by removing /var/lib/apt/lists reduces the image size, since the apt cache is not stored in a layer. Since the RUN statement starts with apt-get update, the package cache will always be refreshed prior to apt-get install.

除此之外,当你通过移除/var/lib/apt/lists来清理apt缓存可减少镜像大小,由于apt缓存不会保存在镜像层。由于RUN语句以apt-get update开头,包缓存将总是得到更新,在运行apt-get install之前。

Note: The official Debian and Ubuntu images automatically run [apt-get clean](https://github.com/moby/moby/blob/03e2923e42446dbb830c654d0eec323a0b4ef02a/contrib/mkimage/debootstrap#L82-L105), so explicit invocation is not required. 注意:官方的Debian和Ubuntu镜像自动运行apt-get clean,因此不要求显式调用。

USING PIPES

使用管道|

Some RUN commands depend on the ability to pipe the output of one command into another, using the pipe character (|), as in the following example:

一些RUN命令取决于使用管道字符 (|) 将一个命令的输出通过管道传输到另一个命令的能力,如下例所示:

RUN wget -O - https://some.site | wc -l > /number

Docker executes these commands using the /bin/sh -c interpreter, which only evaluates the exit code of the last operation in the pipe to determine success. In the example above this build step succeeds and produces a new image so long as the wc -l command succeeds, even if the wget command fails.

Docker 使用/bin/sh-c 解释器执行这些命令,该解释器只评估管道中最后一个操作的退出代码,以确定是否成功。在上面的示例中,只要 wc-l 命令成功,即使 wget 命令会失败,此构建步骤将成功并生成新镜像。
If you want the command to fail due to an error at any stage in the pipe, prepend set -o pipefail && to ensure that an unexpected error prevents the build from inadvertently succeeding. For example:

如果您希望命令由于管道中的任何阶段的错误而失败,请在语句前面添加set-o pipefail &&,以确保意外错误能阻止构建而不是无意中的成功。例如:

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

Note: Not all shells support the -o pipefail option. In such cases (such as the dash shell, which is the default shell on Debian-based images), consider using the exec form of RUN to explicitly choose a shell that does support the pipefail option. For example: 注意:不是所有的shells支持-o pipefail选项。在这些情况下(诸如dash,这是Debian系统镜像的默认shell),请考虑使用RUN的exec形式,显式地选择一个支持pipefail选项的shell。举个例子:

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

CMD

Dockerfile reference for the CMD instruction
The CMD instruction should be used to run the software contained by your image, along with any arguments. CMD should almost always be used in the form of CMD [“executable”, “param1”, “param2”…]. Thus, if the image is for a service, such as Apache and Rails, you would run something likeCMD ["apache2","-DFOREGROUND"]. Indeed, this form of the instruction is recommended for any service-based image.

CMD指令本应用于运行镜像中包含的程序,以及一些参数。CMD本几乎总是以CMD [“executable”, “param1”, “param2”…]的格式。因此,如果镜像是一个service,诸如Apache和Rails,你本将运行像CMD [“apache2”, “-DFORGROUND”]。事实上,对于任何基于service的镜像,推荐使用此指令格式。

In most other cases, CMD should be given an interactive shell, such as bash, python and perl. For example, CMD ["perl", "-de0"], CMD ["python"], or CMD [“php”, “-a”]. Using this form means that when you execute something like docker run -it python, you’ll get dropped into a usable shell, ready to go. CMDshould rarely be used in the manner of CMD [“param”, “param”] in conjunction with [ENTRYPOINT](https://docs.docker.com/v17.09/engine/reference/builder/#entrypoint), unless you and your expected users are already quite familiar with how ENTRYPOINT works.
在大部分其他情况,CMD本应指定一个交互的shell,诸如bash、python和perl。例如,CMD [“perl”, “-de0”],CMD [“python”],或者CMD [“php”, “-a”]。使用此格式意味着当你执行类似docker run -it python,你将进入一个可用的shell,可以开始使用了,CMD应少用CMD [“param”, “param”]的方式,当与ENTRYPOINT一起使用的时候,除非你和您期望的用户已经熟悉ENTRYPOINT工作原理。

EXPOSE

Dockerfile reference for the EXPOSE instruction
The EXPOSE instruction indicates the ports on which a container will listen for connections. Consequently, you should use the common, traditional port for your application. For example, an image containing the Apache web server would use EXPOSE 80, while an image containing MongoDB would use EXPOSE 27017 and so on.

EXPOSE指令指示容器将监听连接的端口。因此,您应该为应用程序使用通用的传统端口。例如,包含 Apache web 服务器的镜像将使用EXPOSE 80,而包含 MongoDB 的镜像将使用EXPOSE 27017 等。

For external access, your users can execute docker run with a flag indicating how to map the specified port to the port of their choice. For container linking, Docker provides environment variables for the path from the recipient container back to the source (ie, MYSQL_PORT_3306_TCP).

对于外部访问,您的用户可在执行docker run的时候指定如何端口映射。对于要连接的容器,Docker提供了环境变量给接受方容器??。

ENV

Dockerfile reference for the ENV instruction
In order to make new software easier to run, you can use ENV to update the PATH environment variable for the software your container installs. For example, ENV PATH /usr/local/nginx/bin:$PATH will ensure that CMD [“nginx”] just works.

为了让心软件更容易运行起来,你可使用ENV来更新环境变量PATH给容器要安装的软件。举个例子,ENV PATH /usr/local/nginx/bin:$PATH,将确保CMD [“nginx”]可正常运行。

The ENV instruction is also useful for providing required environment variables specific to services you wish to containerize, such as Postgres’s PGDATA.

ENV 指令也有助于提供特定于您希望包含的服务的所需环境变量,例如 Postgres 的 PGDATA。
Lastly, ENV can also be used to set commonly used version numbers so that version bumps are easier to maintain, as seen in the following example:

最后,ENV 也可以用来设置常用的版本号,这样版本bump(版本变动?)就更容易维护,如下例所示:

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

Similar to having constant variables in a program (as opposed to hard-coding values), this approach lets you change a single ENV instruction to auto-magically bump the version of the software in your container.

类似于程序中有常量变量 (而不是硬编码值), 这种方法允许您更改单个 ENV 指令,以在容器中神奇地自动更新软件版本。

ADD or COPY

ADD还是COPY

Dockerfile reference for the ADD instruction
Dockerfile reference for the COPY instruction
Although ADD and COPY are functionally similar, generally speaking, COPY is preferred. That’s because it’s more transparent than ADD. COPY only supports the basic copying of local files into the container, while ADDhas some features (like local-only tar extraction and remote URL support) that are not immediately obvious. Consequently, the best use for ADD is local tar file auto-extraction into the image, as in ADD rootfs.tar.xz /.

尽管ADD和COPY的功能相似,通常来说,请有限使用COPY。这是因为它比ADD更透明。COPY值支持基础的拷贝本地文件到容器中,而ADD有一些功能(像仅本地的tar解压和远程支持),这些功能不太明显。结果是,使用ADD的最佳方式是将本地的tar文件自动解压到镜像中,正如在ADD rootfs.tar.xz /

If you have multiple Dockerfile steps that use different files from your context, COPY them individually, rather than all at once. This will ensure that each step’s build cache is only invalidated (forcing the step to be re-run) if the specifically required files change.

如果你的Dockerfile有多个步骤,这些步骤使用上下文的不同文件,那么请单独COPY这些文件,而不是一次性地拷贝所有文件。这将确保每个步骤的构建缓存只有在特定文件变更时才不可用(强迫此步骤重新运行)。
For example:
单独COPY所需文件的举个例子:

COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/

Results in fewer cache invalidations for the RUN step, than if you put the COPY . /tmp/ before it.
与其将COPY . /tmp/ 放在前面,上面的设计让更少缓存无效。

Because image size matters, using ADD to fetch packages from remote URLs is strongly discouraged; you should use curl or wget instead. That way you can delete the files you no longer need after they’ve been extracted and you won’t have to add another layer in your image. For example, you should avoid doing things like:
由于镜像大小的问题,强烈不建议使用ADD来获取远程URL的package;你应使用curl或wget代替。使用那种方式,你可删除不在需要的文件,在他们解压后,你也无需添加另外的镜像层到镜像中。举个例子,你应该避免这么做:

ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

And instead, do something like:
而是应该这么编写:

RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

For other items (files, directories) that do not require ADD’s tar auto-extraction capability, you should always use COPY.
对于其他无需ADD来自动解压的条目(文件、目录),你应总是使用COPY。

ENTRYPOINT

Dockerfile reference for the ENTRYPOINT instruction
The best use for ENTRYPOINT is to set the image’s main command, allowing that image to be run as though it was that command (and then use CMD as the default flags).

ENTRYPOINT的最佳用法是设置镜像的主命令,这允许镜像运行其中的命令(并使用CMD作为默认的flag)。

Let’s start with an example of an image for the command line tool s3cmd:
我们使用一个命令行工具s3cmd的例子开始:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

Now the image can be run like this to show the command’s help:
现在镜像可运行此命令来展示名的帮助信息:

$ docker run s3cmd

Or using the right parameters to execute a command:
或者使用正确的参数来执行一个命令:

$ docker run s3cmd ls s3://mybucket

This is useful because the image name can double as a reference to the binary as shown in the command above.
这很有用,因为镜像名可以double,作为二进制的指引,如上所示。

The ENTRYPOINT instruction can also be used in combination with a helper script, allowing it to function in a similar way to the command above, even when starting the tool may require more than one step.

ENTRYPOINT指令还可与帮助脚本组合使用,实现的功能跟上面的命令类似,尽管使用该工具可能还需要更多的步骤。

For example, the Postgres Official Image uses the following script as its ENTRYPOINT:
举个例子,Postgres官方镜像以下脚本作为它的ENTRYPOINT:

#!/bin/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 "$@"

Note: This script uses the [exec](http://wiki.bash-hackers.org/commands/builtin/exec) Bash command so that the final running application becomes the container’s PID 1. This allows the application to receive any Unix signals sent to the container. See the [ENTRYPOINT](https://docs.docker.com/v17.09/engine/reference/builder/#entrypoint) help for more details. 注意:此脚本使用exec格式,以便最后运行的应用成为该容器的PID 1。这允许该应用接受发送给容器的任何Unix信号。请查看ENTRYPOINT帮助获取更多信息。

The helper script is copied into the container and run via ENTRYPOINT on container start:
帮助脚本被拷贝到容器中,并在容器启动时运行ENTRYPOINT:

COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

This script allows the user to interact with Postgres in several ways.
此脚本允许以多重方式与Postgres交互。

It can simply start Postgres:
它可简单地启动Postgres:

$ docker run postgres

Or, it can be used to run Postgres and pass parameters to the server:
或者,他可用于运行Postgres并传递参数给服务:

$ docker run postgres postgres --help

Lastly, it could also be used to start a totally different tool, such as Bash:
最后,它还可用于启动一个完全不同的工具,例如Bash:

$ docker run --rm -it postgres bash

VOLUME

Dockerfile reference for the VOLUME instruction
The VOLUME instruction should be used to expose any database storage area, configuration storage, or files/folders created by your docker container. You are strongly encouraged to use VOLUME for any mutable and/or user-serviceable parts of your image.

VOLUME指令应该用于公开 docker 容器创建的任何数据库存储区域、配置存储或文件/文件夹。强烈鼓励您将卷VOLUME用于镜像的任何可变和/或用户服务部分。

USER

Dockerfile reference for the USER instruction
If a service can run without privileges, use USER to change to a non-root user. Start by creating the user and group in the Dockerfile with something like RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres.

如果服务可以在没有权限的情况下运行,请使用USER更改为非根用户。首先在 Dockerfile 中创建用户和组,类似于RUN groupadd-r postgres && useradd-no-log-init -r -g postgres postgres。

Note: Users and groups in an image get a non-deterministic UID/GID in that the “next” UID/GID gets assigned regardless of image rebuilds. So, if it’s critical, you should assign an explicit UID/GID. 注意:镜像中的用户和组获得一个非确定的UID/GID,因为不管镜像是否重构下一个UID/GID都会被分配。因此,如果用户很重要,请分配一个显式的UID/GID。

Note: Due to an unresolved bug in the Go archive/tar package’s handling of sparse files, attempting to create a user with a sufficiently large UID inside a Docker container can lead to disk exhaustion as /var/log/faillog in the container layer is filled with NUL (\0) characters. Passing the --no-log-initflag to useradd works around this issue. The Debian/Ubuntu adduser wrapper does not support the --no-log-init flag and should be avoided. 注意:由于处理sparse文件的某个Go包存在未解决的bug,尝试创建一个太大的UID可导致磁盘溢出(exhaustion),因为容器层的/var/log/faillog被填充为NUL(\0)字符。传递一个—no-log-init到useradd命令可以解决此问题。Debian/Ubuntu的adduser wrapper不支持—no-log-init标识,理应避免。

You should avoid installing or using sudo since it has unpredictable TTY and signal-forwarding behavior that can cause more problems than it solves. If you absolutely need functionality similar to sudo (e.g., initializing the daemon as root but running it as non-root), you may be able to use “gosu”.

你应避免使用sudo来安装或使用,因为它有不可预测的TTY和信号转换,这可引起更多问题,比起它可解决的。如果你真的需要类似sudo的功能(例如,以root初始化daemon,但运行时需要以非root),你可使用“gosu”。

Lastly, to reduce layers and complexity, avoid switching USER back and forth frequently.
最后,为了减少镜像层和复杂度,请避免频繁换切换USER。

WORKDIR

Dockerfile reference for the WORKDIR instruction
For clarity and reliability, you should always use absolute paths for your WORKDIR. Also, you should use WORKDIR instead of proliferating instructions like RUN cd … && do-something, which are hard to read, troubleshoot, and maintain.

为了清晰可靠,您应该始终为WORKDIR使用绝对路径。此外,您应该使用WORKDIR,而不是像RUN cd… && 做一些难以阅读、故障排除和维护的事情这样的指令。

ONBUILD

Dockerfile reference for the ONBUILD instruction
An ONBUILD command executes after the current Dockerfile build completes. ONBUILD executes in any child image derived FROM the current image. Think of the ONBUILD command as an instruction the parent Dockerfile gives to the child Dockerfile.

ONBUILD 命令在当前 Dockerfile 构建完成后执行。ONBUILD 在从当前镜像导出的任何子镜像中执行。请将 ONBUILD 命令视为父 Dockerfile 给子 Dockerfile 的指令。

A Docker build executes ONBUILD commands before any command in a child Dockerfile.

Docker构建进程在子 Dockerfile 中的任何命令之前执行 ONBUILD 命令。

ONBUILD is useful for images that are going to be built FROM a given image. For example, you would use ONBUILD for a language stack image that builds arbitrary user software written in that language within theDockerfile, as you can see in Ruby’s [ONBUILD](https://github.com/docker-library/ruby/blob/master/2.4/jessie/onbuild/Dockerfile) variants.

ONBUILD 对于将从给定镜像构建的镜像很有用。例如,您可以将 ONBUILD 用于语言堆栈镜像,该镜像构建在 dockerfile 中使用该语言编写的任意用户软件,正如您在 Ruby 的 ONBUILD 变体中看到的那样。

Images built from ONBUILD should get a separate tag, for example: ruby:1.9-onbuild or ruby:2.0-onbuild.

从 ONBUILD 构建的镜像应该得到一个单独的标签,例如: ruby:1.9-onbuild 或 ruby:2.0-onbuild。

Be careful when putting ADD or COPY in ONBUILD. The “onbuild” image will fail catastrophically if the new build’s context is missing the resource being added. Adding a separate tag, as recommended above, will help mitigate this by allowing the Dockerfile author to make a choice.

在 ONBUILD 中使用ADD或COPY时要小心。如果新构建的上下文缺少正在添加的资源,“onbuild” 镜像将会灾难性地失败。如上所述,添加一个单独的标签将通过允许 Dockerfile 作者做出选择来帮助缓解这种情况。

Examples for Official Repositories

These Official Repositories have exemplary Dockerfiles:
这些官方仓库有Dockerfile模板:

parent image, images, dockerfile, best practices, hub, official repo