编写Dockerfile的最佳实践

编写Dockerfile的最佳实践

本文档介绍了用于构建有效镜像的最佳实践和方法。

Docker通过从一个Dockerfile文本文件中读取指令来自动构建镜像,该文本文件按顺序包含构建给定镜像所需的所有命令。一个Dockerfile遵循特定的格式和指令集,您可以在Dockerfilereference中找到。

Docker镜像由一些只读层组成,每个只读层对应于一个Dockerfile指令。这些层是堆叠的,每个层都是前一层的变化增量。考虑一下Dockerfile

FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

每条指令创建一层:

  • FROMubuntu:18.04 Docker基础镜像创建一层。
  • COPY 从Docker客户端的当前目录添加文件。
  • RUN 使用make构建您的应用程序。
  • CMD 指定在容器中运行什么命令。

运行镜像并生成容器时,你可以在基础层之上添加一个新的可写层(“容器层”)。对运行中的容器所做的所有更改(例如写入新文件,修改现有文件和删除文件)都将写入此可写容器层。

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

一般准则和建议

创建临时容器

Dockerfile所定义的镜像应生成尽可能是短暂的容器。“短暂”是指可以停止并销毁容器,然后对其进行重建和替换,并采用绝对的最低限度的设置和配置。

请参阅“十二因子应用程序” 方法下的“ Porcesses ” ,以了解以这种无状态方式运行容器的动机。

了解构建环境(上下文Context)

发出docker build命令时,当前的工作目录称为构建上下文。默认情况下,Dockerfile被认定位于当前(bash进入的目录)目录,但是您可以使用文件标志(-f)指定其他位置。无论Dockerfile实际位于何处,当前目录(所以一定要注意构建的当前目录,不然生成的镜像会很大)中文件和目录的所有递归内容都将作为构建上下文发送到Docker守护程序。

ps~ Docker 是C/S架构,我们在客户端执行所有命令,构建事实上是把Dockerfile和需要的文件发送到Docker daemon

构建上下文示例

为构建上下文创建一个目录并cd进入该目录。将“ hello”写入名为hello的文本文件中,然后创建一个Dockerfile ,在其中运行cat 。从构建上下文(.)中构建镜像:

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

ps~ Dockerfile 内容:
FROM busybox
COPY /hello /
RUN cat /hello

移动Dockerfilehello进入单独的目录,并构建镜像的第二个版本(不依赖于上次构建的缓存)。使用-f 以指向Dockerfile并指定构建上下文目录:

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

包含构建镜像不需要的文件会导致较大的构建上下文和较大的镜像大小。这会增加生成镜像的时间,pull和push镜像的时间以及容器运行时的大小。要查看您的构建上下文有多大,请在构建您的Dockerfile时查找如下消息:

Sending build context to Docker daemon  187.8MB

在这里插入图片描述

通过stdin发送Dockerfile

具有本地或远程构建上下文的Docker通过stdin发送Dockerfile来构建镜像。通过stdin发送Dockerfile可以执行一次性构建,无需编写Dockerfile到磁盘上,或者生成Dockerfile不持久化的情形。

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

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

您可以使用首选方法或最适合您的用例的方法替换示例。

使用来自STDIN的DOCKERFILE构建镜像,而无需发送构建上下文

使用此语法,可使用写入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进行排除。

注意:如果使用此语法,使用COPYADDDockerfile构建将失败。以下示例说明了这一点:

# 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

从本地的构建上下文构建,使用STDIN发送DOCKERFILE

使用此语法可使用本地文件系统上的文件,并使用stdin输入Dockerfile来构建镜像。该语法使用-f(或--file)选项来指定可用的Dockerfile,使用连字符(-)作为文件名,指示Docker从stdin读取Dockerfile

docker build [OPTIONS] -f- PATH

下面的示例使用当前目录(.)作为构建上下文,并使用一个stdin传送的Dockerfile构建一个镜像

# 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

从远程的构建上下文构建,使用STDIN发送DOCKERFILE

用这种语法,使用远程git仓库中的文件来构建镜像,并且这种情形直接使用stdin发送Dockerfile。该语法使用-f(或--file)选项来指定可用的Dockerfile,使用连字符(-)作为文件名,指示Docker从stdin读取Dockerfile

docker build [OPTIONS] -f- PATH

这种语法从一个不包含Dockerfile的远程仓库来构建镜像,在一些情形下是非常有用的。或者,你想使用一个自定义的Dockerfile构建,而不用维持fork这个仓库。
下面的例子使用一个stdin写入的Dockerfille构建一个镜像,并且从Github的远程仓库添加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时,需要Docker主机安装git。

用.dockerignore排除

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

使用多阶段构建

多阶段构建使您可以大幅度减小最终镜像的大小,而不必努力减少中间层和文件的数量。

由于镜像是在生成过程的最后阶段生成的,因此可以利用生成缓存来最小化镜像层。

例如,如果您的构建包含多个层,则可以将它们从更改频率较低(以确保生成缓存可重用)到更改频率较高的顺序排序:

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

  • 安装或更新库依赖项

  • 生成您的申请

Go应用程序的Dockerfile可能类似于:

FROM golang:1.11-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"]

不要安装不必要的软件包

为了降低复杂性,依赖性,文件大小和构建时间,请避免仅仅因为它们“很容易安装”而安装多余或不必要的软件包。例如,您不需要在数据库镜像中包括文本编辑器。

解耦应用程序

每个容器应该只有一个方面。将应用程序解耦到多个容器中,可以更轻松地水平缩放和重复使用容器。例如,一个Web应用程序堆栈可能由三个单独的容器组成,每个容器都有自己的唯一镜像,以分离的方式管理Web应用程序,数据库和内存中的缓存。

将每个容器限制为一个进程是一个很好的经验法则,但这并不是一成不变的规则。例如,不仅可以使用初始化进程来生成容器 ,而且某些程序还可以自行生成其他进程。例如,Celery可以产生多个工作进程,而Apache可以为每个请求创建一个进程。

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

最小化层数

在较旧的Docker版本中,务必最小化映像中的层数以确保其性能。添加了以下功能来减少此限制:

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

  • 尽可能使用多阶段构建,并且仅将所需的工件复制到最终镜像中。这使您可以在中间构建阶段中包含工具和调试信息,而无需增加最终映像的大小。

排序多行参数

只要有可能,就可以通过字母数字排序多行参数来简化以后的更改。这有助于避免软件包重复,并使列表更易于更新。这也使PRs(官网直译我也不知道具体含义)易于阅读和查看。在反斜杠(\)之前添加空格也有帮助。

下面是来自一个示例buildpack-deps镜像:

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

利用构建缓存

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

如果根本不想使用缓存,则命令docker build上可以使用--no-cache=true 选项。但是,如果您确实允许Docker使用其缓存,那么了解何时可以找到匹配的镜像非常重要。Docker遵循的基本规则概述如下:

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

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

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

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

一旦缓存无效,所有后续Dockerfile命令都会生成新映像,并且不使用缓存。

Dockerfile指令

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

FROM

Dockerfile的FROM指令参考

尽可能使用当前的官方镜像作为镜像的基础。我们建议使用Alpine映像(小而完整的Linux镜像),因为它受到严格控制且尺寸较小(当前小于5 MB),同时仍是完整的Linux发行版。

格式

FROM [--platform=<platform>] <image> [AS <name>]

或者

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

或者

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

FROM指令初始化一个新的构建阶段,并为后续指令设置基本映像。因此,有效的Dockerfile必须从FROM指令开始。该镜像可以是任何有效的镜像——源自于公共库的,容易pull的。

  • DockerfileARG是仅先于FROM的指令(ARG可以写在FROM前面)
ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras
  • FROM可以在一个Dockerfile红出现多次,实现多阶段构建。每一个阶段可是使用AS name起名字,后续可以使用FROMCOPY --from=<name|index>引用。
  • tagdigest(摘要就是镜像使用摘要算法生成的那一长串字符)是可选的,忽略他们,会被赋予latest

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 apt-get upgradedist-upgrade,因为许多来自父映像的“基本”程序包无法在无特权的容器内升级。如果基础镜像中的某个包过时了,你应该联系它的维护者。如果你确定某个特定的包,比如 foo,需要升级,使用 apt-get install -y foo 就行,该指令会自动升级 foo 包。

永远记住将 RUN apt-get update 和 apt-get install 组合成⼀条 RUN 语句,例如:

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

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

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

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

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缓存,从而减小了镜像大小。由于该 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选项。

在诸如dash基于Debian的镜像上的shell之类的情况下,请考虑使用exec形式的RUN显式选择确实支> 持该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 ["PHP", "-a"]。使用这种形式意味着,当你执行类似 docker run -it python 时,你会进⼊⼀个准备好的 shell 中。 CMD 在极少的情况下才会以 CMD ["param", "param"] 的式与 ENTRYPOINT 协同使用,除非你和你的镜像使用者都对 ENTRYPOINT 的工作方式十分熟悉。

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

该脚本允许用户以多种方式与Postgres进行交互。

它可以简单地启动Postgres:

$ docker run postgres

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

$ docker run postgres postgres --help

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

$ docker run --rm -it postgres bash

CMD与ENTRYPOINT区别
菜鸟教程演示CMD和ENTRYPOINT的使用

EXPOSE

Dockerfile的EXPOSE指令参考

该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’s PGDATA

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

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

类似于在程序中常量(与硬编码值相反),此方法使您可以更改一条ENV指令以自动地更改容器中软件的版本。

每ENV行都创建一个新的中间层,就像RUN命令一样。这意味着,即使您在以后的层中取消(unset)设置环境变量,它也仍将保留在该层中,并且其值也无法转储。您可以通过创建如下所示的Dockerfile,然后对其进行构建test ,执行test。

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

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

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或COPY

用于ADD指令的Dockerfile参考
COPY指令的Dockerfile参考
尽管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 . /tem/放到RUN之前,这种会导致RUN步骤更少的缓存校验。

由于镜像大小很重要,ADD因此强烈建议不要使用从远程URL获取软件包。您应该使用curlwget代替。这样,您可以在提取文件后删除不再需要的文件,而不必在镜像中添加另一层。例如,您应该避免做以下事情:

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

相反,请执行以下操作:

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

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

VOLUME

VOLUME指令的Dockerfile参考

VOLUME指令应用于指明由Docker容器创建的任何数据库存储区、配置存储或文件/文件夹。对于镜像任何可变的和user-serviceable部分,强烈推荐你使用VOLUME

USER

USER指令的Dockerfile参考

如果服务可以在没有特权的情况下运行,请使用USER更改为非root用户。开始之前,通过RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres方式在Dockerfile中创建用户与组。

考虑一个明确的UID / GID

镜像中的用户和组被分配了不确定的UID / GID,因为无论镜像如何重建,都会分配“下一个” UID / GID。因此,如果很关键,则应分配一个明确的UID / GID。

由于Go archive/tar软件包处理稀疏文件中的一个未解决的bug,尝试在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参考

一个ONBUILD命令将在Dockerfile构建完成后执行。 ONBUILD当前镜像的任何子镜像中执行。将该ONBUILD命令视为父Dockerfile给予子Dockerfile的指令。

Docker会在任何子Dockerfile指令前构建执行ONBUILD命令

ONBUILD从一个给定的镜像打镜像是有用的。For example, you would use ONBUILD for a language stack image that builds arbitrary user software written in that language within the Dockerfile, as you can see in Ruby’s ONBUILD variants.

使用ONBUILD生成的镜像应获得一个单独的标签,例如: ruby:1.9-onbuildruby:2.0-onbuild

ONBUILD中放入ADDCOPY要小心。如果新构建的上下文缺少要添加的资源,则“ onbuild”映像将灾难性地失败。如上所述,添加一个单独的标签可以允许Dockerfile作者做出选择,从而缓解这种情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值