Best practices for writing Dockerfiles

预计阅读时间:31分钟

本文档涵盖了构建高效映像的推荐最佳实践和方法.

Docker 通过读取Dockerfile中的指令自动构建镜像——一个包含构建给定镜像所需的所有命令的文本文件. Dockerfile遵循特定格式和指令集,您可以在Dockerfile 参考中找到.

一个 Docker 镜像由只读层组成,每个层代表一个 Dockerfile 指令. 这些层是堆叠的,每一层都是前一层变化的增量. 考虑这个Dockerfile

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

每条指令创建一个层:

  • FROMubuntu:18.04 Docker 映像创建一个层.
  • COPY从 Docker 客户端的当前目录添加文件.
  • RUN builds your application with make.
  • CMD指定要在容器中运行的命令.

当你运行一个镜像并生成一个容器时,你会在底层之上添加一个新的可写层("容器层"). 对正在运行的容器所做的所有更改,例如写入新文件、修改现有文件和删除文件,都会写入此可写容器层.

有关映像层(以及 Docker 如何构建和存储映像)的更多信息,请参阅关于存储驱动程序.

General guidelines and recommendations

Create ephemeral containers

Dockerfile定义的镜像应该生成尽可能短暂的容器. "临时"是指容器可以停止和销毁,然后用绝对最小的设置和配置重建和替换.

请参阅十二因素应用程序方法下的流程,以了解以这种无状态方式运行容器的动机.

Understand build context

当您发出docker build命令时,当前工作目录称为build context . 默认情况下,假定 Dockerfile 位于此处,但您可以使用文件标志 ( -f ) 指定不同的位置. 无论Dockerfile实际存在于何处,当前目录中文件和目录的所有递归内容都会作为构建上下文发送到 Docker 守护进程.

构建上下文示例

为构建上下文创建一个目录并cd进入它. 将"hello"写入名为hello的文本文件中,并创建一个在其上运行cat的 Dockerfile. 从构建上下文 ( . ) 中构建图像:

$ mkdir myproject && cd myproject
$ echo "hello" > hello
$ echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfile
$ docker build -t helloapp:v1 .

Dockerfilehello移动到单独的目录中并构建映像的第二个版本(不依赖于上次构建的缓存). 使用-f指向 Dockerfile 并指定构建上下文的目录:

$ mkdir -p dockerfiles context
$ mv Dockerfile dockerfiles && mv hello context
$ docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context

无意中包含构建映像不需要的文件会导致更大的构建上下文和更大的映像大小. 这会增加构建镜像的时间、拉取和推送镜像的时间以及容器运行时的大小. 要查看您的构建上下文有多大,请在构建Dockerfile时查找类似这样的消息:

Sending build context to Docker daemon  187.8MB

Pipe Dockerfile through stdin

Docker 能够通过使用本地或远程构建上下文通过stdinDockerfile管道化来构建图像. 在不将 Dockerfile 写入磁盘的情况下,或者在生成Dockerfile且不应在之后持续存在的情况下,通过stdinDockerfile管道化对于执行一次性构建非常有用.

为方便起见,本节中的示例使用此处的文档,但可以使用任何在stdin上提供Dockerfile的方法.

例如,以下命令是等效的:

echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -
docker build -<<EOF
FROM busybox
RUN echo "hello world"
EOF

您可以用您喜欢的方法或最适合您的用例的方法替换这些示例.

Build an image using a Dockerfile from stdin, without sending build context

使用此语法使用来自stdinDockerfile构建映像,而无需发送其他文件作为构建上下文. 连字符 ( - ) 占据PATH的位置,并指示 Docker 从stdin而不是目录读取构建上下文(仅包含一个Dockerfile ):

docker build [OPTIONS] -

以下示例使用通过stdin传递的Dockerfile构建映像. 没有文件作为构建上下文发送到守护进程.

docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF

Dockerfile不需要将文件复制到映像中并提高构建速度的情况下,省略构建上下文可能很有用,因为没有文件发送到守护进程.

如果您想通过从构建上下文中排除某些文件来提高构建速度,请参阅使用 .dockerignore 排除.

Note: Attempting to build a Dockerfile that uses COPY or ADD will fail if this syntax is used. The following example illustrates this:

# create a directory to work in
mkdir example
cd example

# create an example file
touch somefile.txt

docker build -t myimage:latest -<<EOF
FROM busybox
COPY somefile.txt ./
RUN cat /somefile.txt
EOF

# observe that the build fails
...
Step 2/3 : COPY somefile.txt ./
COPY failed: stat /var/lib/docker/tmp/docker-builder249218248/somefile.txt: no such file or directory

Build from a local build context, using a Dockerfile from stdin

使用此语法使用本地文件系统上的文件构建映像,但使用来自stdinDockerfile . 该语法使用-f (或--file )选项来指定要使用的Dockerfile ,使用连字符 ( - ) 作为文件名来指示 Docker 从stdin读取Dockerfile

docker build [OPTIONS] -f- PATH

下面的示例使用当前目录 ( . ) 作为构建上下文,并使用Dockerfile构建映像,该stdin使用here document通过标准输入.

# create a directory to work in
mkdir example
cd example

# create an example file
touch somefile.txt

# build an image using the current directory as context, and a Dockerfile passed through stdin
docker build -t myimage:latest -f- . <<EOF
FROM busybox
COPY somefile.txt ./
RUN cat /somefile.txt
EOF

Build from a remote build context, using a Dockerfile from stdin

使用此语法使用来自远程git存储库的文件,使用来自stdinDockerfile构建映像. 该语法使用-f (或--file )选项来指定要使用的Dockerfile ,使用连字符 ( - ) 作为文件名来指示 Docker 从stdin读取Dockerfile

docker build [OPTIONS] -f- PATH

在您想从不包含Dockerfile的存储库构建映像的情况下,或者如果您想使用自定义Dockerfile构建而不维护您自己的存储库分支,此语法可能很有用.

下面的示例使用来自stdinDockerfile构建图像,并从GitHub 上的"hello-world"Git 存储库添加hello.c文件.

docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c ./
EOF

在引擎盖下

当使用远程 Git 存储库作为构建上下文构建映像时,Docker 在本地机器上执行存储库的git clone ,并将这些文件作为构建上下文发送到守护程序. 此功能需要在您运行docker build命令的主机上安装git .

Exclude with .dockerignore

要排除与构建无关的文件(不重组源存储库),请使用.dockerignore文件. 此文件支持类似于.gitignore文件的排除模式. 有关创建的信息,请参阅.dockerignore 文件.

Use multi-stage builds

多阶段构建允许您大幅减小最终图像的大小,而无需努力减少中间层和文件的数量.

由于镜像是在构建过程的最后阶段构建的,因此您可以通过利用构建缓存来最小化镜像层.

例如,如果您的构建包含多个层,您可以将它们从不太频繁更改(以确保构建缓存可重用)排序到更频繁更改:

  • 安装构建应用程序所需的工具

  • 安装或更新库依赖项

  • 生成您的应用程序

Go 应用程序的 Dockerfile 可能如下所示:

# syntax=docker/dockerfile:1
FROM golang:1.16-alpine AS build

# Install tools required for project
# Run `docker build --no-cache .` to update dependencies
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep

# List project dependencies with Gopkg.toml and Gopkg.lock
# These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies
RUN dep ensure -vendor-only

# Copy the entire project and build it
# This layer is rebuilt when a file changes in the project directory
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"]

Don’t install unnecessary packages

为了减少复杂性、依赖关系、文件大小和构建时间,请避免安装额外或不必要的软件包,因为它们可能"很高兴拥有". 例如,您不需要在数据库图像中包含文本编辑器.

Decouple applications

每个容器应该只有一个关注点. 将应用程序解耦到多个容器中,可以更轻松地水平扩展和重用容器. 例如,一个 Web 应用程序堆栈可能由三个独立的容器组成,每个容器都有自己独特的图像,以分离的方式管理 Web 应用程序、数据库和内存缓存.

将每个容器限制为一个进程是一个很好的经验法则,但这不是一个硬性规定. 例如,不仅可以使用 init 进程生成容器,某些程序可能会自行生成其他进程. 例如, Celery可以产生多个工作进程,而Apache可以为每个请求创建一个进程.

使用您的最佳判断来保持容器尽可能清洁和模块化. 如果容器相互依赖,您可以使用Docker 容器网络来确保这些容器可以通信.

Minimize the number of layers

在旧版本的 Docker 中,尽量减少镜像中的层数以确保它们的性能非常重要. 添加了以下功能以减少此限制:

  • 只有指令RUNCOPYADD创建层. 其他指令创建临时中间图像,并且不增加构建的大小.

  • 在可能的情况下,使用多阶段构建,并且只将您需要的工件复制到最终图像中. 这允许您在中间构建阶段包含工具和调试信息,而不会增加最终映像的大小.

Sort multi-line arguments

只要有可能,通过按字母数字排序多行参数来简化以后的更改. 这有助于避免重复包并使列表更容易更新. 这也使 PR 更容易阅读和审查. 在反斜杠 ( \ ) 前添加一个空格也有帮助.

下面是buildpack-deps映像中的一个示例:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion \
  && rm -rf /var/lib/apt/lists/*

Leverage build cache

构建映像时,Docker 会逐步执行Dockerfile中的指令,并按照指定的顺序执行每个指令. 在检查每条指令时,Docker 在其缓存中查找可以重用的现有映像,而不是创建新的(重复的)映像.

如果您根本不想使用缓存,可以在docker build命令上使用--no-cache=true选项. 但是,如果您确实让 Docker 使用它的缓存,重要的是要了解它何时可以找到匹配的图像,何时不能找到匹配的图像. Docker 遵循的基本规则概述如下:

  • 从已经在缓存中的父图像开始,将下一条指令与从该基础图像派生的所有子图像进行比较,以查看其中一个是否是使用完全相同的指令构建的. 如果不是,则缓存无效.

  • 在大多数情况下,只需将Dockerfile中的指令与其中一个子映像进行比较就足够了. 但是,某些说明需要更多的检查和解释.

  • 对于ADDCOPY指令,检查图像中文件的内容并为每个文件计算校验和. 这些校验和中不考虑文件的最后修改时间和最后访问时间. 在缓存查找期间,将校验和与现有图像中的校验和进行比较. 如果文件中有任何更改,例如内容和元数据,则缓存无效.

  • 除了ADDCOPY命令之外,缓存检查不会查看容器中的文件来确定缓存匹配. 例如,在处理RUN apt-get -y update命令时,不会检查容器中更新的文件以确定是否存在缓存命中. 在这种情况下,只有命令字符串本身用于查找匹配项.

一旦缓存失效,所有后续Dockerfile命令都会生成新的镜像并且缓存不会被使用.

Dockerfile instructions

这些建议旨在帮助您创建高效且可维护的Dockerfile .

FROM

FROM 指令的 Dockerfile 参考

尽可能使用当前的官方图像作为图像的基础. 我们推荐使用Alpine 映像,因为它受到严格控制且体积小(目前低于 6 MB),同时仍然是一个完整的 Linux 发行版.

LABEL

了解对象标签

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

必须引用带有空格的字符串,或者必须对空格进行转义. 内引号字符 ( " ),也必须转义.

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

一张图片可以有多个标签. 在 Docker 1.10 之前,建议将所有标签组合成一条LABEL指令,以防止创建额外的层. 这不再是必需的,但仍然支持组合标签.

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

上式也可以写成:

# 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"

有关可接受的标签键和值的指南,请参阅了解对象标签. 查询标签请参考管理对象标签中的过滤相关项. 另请参阅 Dockerfile 参考中的LABEL .

RUN

RUN 指令的 Dockerfile 参考

将长或复杂的RUN语句拆分为用反斜杠分隔的多行,以使您的Dockerfile更具可读性、可理解性和可维护性.

apt-get

RUN最常见的用例可能是apt-get的应用程序. 因为它会安装软件包,所以RUN apt-get命令有几个需要注意的问题.

始终在同一RUN语句中将RUN apt-get updateapt-get install结合使用. 例如:

RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo  \
    && rm -rf /var/lib/apt/lists/*

RUN语句中单独使用apt-get update会导致缓存问题,并且后续apt-get install指令会失败. 例如,假设您有一个 Dockerfile:

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl

构建镜像后,所有层都在 Docker 缓存中. 假设您稍后通过添加额外的包来修改apt-get install

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl nginx

Docker 将初始指令和修改后的指令视为相同,并重用之前步骤中的缓存. 结果apt-get update没有被执行,因为构建使用了缓存的版本. 因为apt-get update未运行,您的构建可能会获得curlnginx软件包的过时版本.

使用RUN apt-get update && apt-get install -y可确保您的 Dockerfile 安装最新的软件包版本,无需进一步编码或手动干预. 这种技术被称为"缓存清除". 您还可以通过指定包版本来实现缓存清除. 这称为版本固定,例如:

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

版本固定强制构建检索特定版本,而不管缓存中有什么. 这种技术还可以减少由于所需包的意外更改而导致的故障.

下面是一个格式良好的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/*

s3cmd参数指定版本1.1.* . 如果映像之前使用的是旧版本,则指定新版本会导致apt-get update缓存崩溃并确保安装新版本. 在每一行列出包也可以防止包重复的错误.

此外,当您通过删除/var/lib/apt/lists来清理 apt 缓存时,它会减小图像大小,因为 apt 缓存没有存储在层中. 由于RUN语句以apt-get update开头,包缓存总是在apt-get install之前刷新.

官方 Debian 和 Ubuntu 映像会自动运行apt-get clean ,因此不需要显式调用.

Using pipes

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

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

Docker 使用/bin/sh -c解释器执行这些命令,该解释器仅评估管道中最后一个操作的退出代码以确定成功. 在上面的示例中,只要wc -l命令成功,即使wget命令失败,此构建步骤也会成功并生成新映像.

如果您希望命令由于管道中任何阶段的错误而失败,请在前面set -o pipefail &&以确保意外错误可以防止构建意外成功. 例如:

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

并非所有 shell 都支持-o pipefail选项.

在基于 Debian 的映像上的dash shell 等情况下,请考虑使用RUNexec形式显式选择支持pipefail选项的 shell. 例如:

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

CMD

CMD 指令的 Dockerfile 参考

CMD指令应用于运行映像中包含的软件以及任何参数. CMD应该几乎总是以CMD ["executable", "param1", "param2"…]的形式使用. 因此,如果图像用于服务,例如 Apache 和 Rails,您将运行CMD ["apache2","-DFOREGROUND"]类的东西. 事实上,这种形式的指令推荐用于任何基于服务的图像.

在大多数其他情况下,应为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

EXPOSE 指令的 Dockerfile 参考

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

对于外部访问,您的用户可以使用指示如何将指定端口映射到他们选择的端口的标志执行docker run . 对于容器链接,Docker 为从接收容器返回到源容器的路径提供环境变量(即MYSQL_PORT_3306_TCP ).

ENV

ENV 指令的 Dockerfile 参考

为了使新软件更易于运行,您可以使用ENV为您的容器安装的软件更新PATH环境变量. 例如, ENV PATH=/usr/local/nginx/bin:$PATH确保CMD ["nginx"]正常工作.

ENV指令还可用于提供特定于您希望容器化的服务所需的环境变量,例如 Postgres 的PGDATA .

最后, ENV还可以用于设置常用的版本号,以便更容易维护版本颠簸,如下例所示:

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

类似于在程序中拥有常量变量(与硬编码值相反),这种方法允许您更改单个ENV指令以自动神奇地提升容器中的软件版本.

每个ENV行都会创建一个新的中间层,就像RUN命令一样. 这意味着即使您在将来的层中取消设置环境变量,它仍然会保留在该层中,并且可以转储其值. 您可以通过创建如下所示的 Dockerfile 来测试它,然后构建它.

# syntax=docker/dockerfile:1
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
$ docker run --rm test sh -c 'echo $ADMIN_USER'

mark

为了防止这种情况,并真正取消设置环境变量,请使用带有 shell 命令的RUN命令,在单个层中设置、使用和取消设置变量. 你可以用;分隔你的命令或&& . 如果您使用第二种方法,并且其中一个命令失败,则docker build也会失败. 这通常是个好主意. 使用\作为 Linux Dockerfiles 的续行符可以提高可读性. 您还可以将所有命令放入一个 shell 脚本,并让RUN命令运行该 shell 脚本.

# syntax=docker/dockerfile:1
FROM alpine
RUN export ADMIN_USER="mark" \
    && echo $ADMIN_USER > ./mark \
    && unset ADMIN_USER
CMD sh
$ docker run --rm test sh -c 'echo $ADMIN_USER'

ADD or COPY

虽然ADDCOPY在功能上相似,但一般来说,首选COPY . 那是因为它比ADD更透明. COPY仅支持将本地文件基本复制到容器中,而ADD具有一些并非立即显而易见的功能(如仅本地 tar 提取和远程 URL 支持). 因此, ADD的最佳用途是将本地 tar 文件自动提取到映像中,如ADD rootfs.tar.xz /中.

如果您有多个Dockerfile步骤使用来自您的上下文的不同文件,请单独COPY它们,而不是一次全部复制. 这确保了每个步骤的构建缓存仅在特定需要的文件发生更改时才失效(强制重新运行该步骤).

例如:

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

与放置COPY . /tmp/相比,导致RUN步骤的缓存失效更少. COPY . /tmp/在它之前.

因为图像大小很重要,所以强烈建议不要使用ADD从远程 URL 获取包; 您应该改用curlwget . 这样,您可以在提取文件后删除不再需要的文件,而不必在图像中添加另一层. 例如,您应该避免执行以下操作:

ADD https://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

相反,请执行以下操作:

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

对于不需要ADD的 tar 自动提取功能的其他项目(文件、目录),您应该始终使用COPY .

ENTRYPOINT

ENTRYPOINT 指令的 Dockerfile 参考

ENTRYPOINT的最佳用途是设置图像的主命令,允许该图像像该命令一样运行(然后使用CMD作为默认标志).

让我们从命令行工具s3cmd的图像示例开始:

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

现在可以像这样运行图像以显示命令的帮助:

$ docker run s3cmd

或者使用正确的参数来执行命令:

$ docker run s3cmd ls s3://mybucket

这很有用,因为图像名称可以兼作对二进制文件的引用,如上面的命令所示.

ENTRYPOINT指令也可以与帮助脚本结合使用,允许它以与上述命令类似的方式运行,即使在启动工具时可能需要多个步骤.

例如, Postgres Official Image使用以下脚本作为其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 "$@"

将应用程序配置为 PID 1

此脚本使用exec Bash 命令,以便最终运行的应用程序成为容器的 PID 1.这允许应用程序接收发送到容器的任何 Unix 信号. 有关更多信息,请参阅ENTRYPOINT参考.

辅助脚本被复制到容器中,并在容器启动时通过ENTRYPOINT运行:

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

This script allows the user to interact with Postgres in several ways.

它可以简单地启动 Postgres:

$ docker run postgres

或者,它可以用于运行 Postgres 并将参数传递给服务器:

$ docker run postgres postgres --help

最后,它还可以用于启动一个完全不同的工具,例如 Bash:

$ docker run --rm -it postgres bash

VOLUME

VOLUME 指令的 Dockerfile 参考

VOLUME指令应该用于公开任何由 docker 容器创建的数据库存储区域、配置存储或文件/文件夹. 强烈建议您将VOLUME用于图像的任何可变和/或用户可维护的部分.

USER

USER 指令的 Dockerfile 参考

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

考虑一个显式的 UID/GID

映像中的用户和组被分配了一个不确定的 UID/GID,因为无论映像重建如何,都会分配"下一个"UID/GID. 因此,如果它很重要,您应该分配一个显式的 UID/GID.

由于 Go 归档/tar 包处理稀疏文件的一个未解决的错误,尝试在 Docker 容器内创建具有非常大 UID 的用户可能会导致磁盘耗尽,因为容器层中的/var/log/faillog被填充NULL (\0) 字符. 一种解决方法是将--no-log-init标志传递给 useradd. Debian/Ubuntu adduser包装器不支持此标志.

避免安装或使用sudo ,因为它具有不可预测的 TTY 和可能导致问题的信号转发行为. 如果您绝对需要类似于sudo的功能,例如将守护进程初始化为root但以非root身份运行,请考虑使用"gosu" .

最后,为了减少层次和复杂性,避免频繁地来回切换USER .

WORKDIR

WORKDIR 指令的 Dockerfile 参考

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

ONBUILD

ONBUILD 指令的 Dockerfile 参考

当前Dockerfile构建完成后,将执行ONBUILD命令. ONBUILDFROM当前图像派生的任何子图像中执行. 将ONBUILD命令视为父Dockerfile给子Dockerfile的指令.

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

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

使用ONBUILD构建的图像应该有一个单独的标签,例如: ruby:1.9-onbuildruby:2.0-onbuild .

ADDCOPY放入ONBUILD时要小心. 如果新构建的上下文缺少要添加的资源,则"onbuild"映像会灾难性地失败. 如上所述,添加一个单独的标签有助于通过允许Dockerfile作者做出选择来缓解这种情况.

Examples of Docker Official Images

这些官方图像具有示例性Dockerfile

Additional resources:

父图像, images, dockerfile, 最佳实践, hub, 官方形象

by  icopy.site