Dockerfile的最佳实践

简介

Docker经过读取Dockerfile文件中的指令自动构建镜像。Dockerfile文件为一个文本文件,里面包含构建镜像所需的全部的命令。Dockerfile文件遵循特定的格式和指令集 Docker镜像由只读层组成,每一个层都表明一个Dockerfile指令。这些层是堆叠的,每一个层都是前一层变化的增量。示例:php

FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
复制代码

每一个指令构建一层: FROM 根据ubuntu18.04docker镜像建立一个层 COPY 将Docker客户端当前的目录文件添加到镜像中 RUN 使用make构建应用程序 CMD 指定在容器中运行的命令前端

经过镜像启动一个容器的时候,会在基础层上添加一个可写层。对正在运行的容器所作的全部更改(例如,写入新文件,修改现有文件和删除文件)都将写入此可写容器层。python

建议

1.建立临时容器

经过Dockerfile文件定义的镜像,产生的容器尽量的是临时的。所谓的临时,意思是,容器可中止,销毁,重建和替代为最小设置和配置。linux

2.理解构建上下文

当你发出一个docker build命令时,当前目录将做为构建的上下文。Dockerfile默认存放在此目录下。可是,你能够经过-f选项指定一个不一样的目录。不论Dockerfile文件实际存放在何处。当前目录中的全部文件和目录的递归内容都将做为构建上下文发送到Docker守护程序。nginx

示例: 建立一个用于构建上下文的目录,并cd进入该目录下。写入"Hello"字符串到一个文本文件中,命名为hello并建立一个Dockerfile文件运行cat命令,经过.构建上下文构建镜像。git

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

将Dockerfile和hello移到不一样的目录下,构建第二版镜像(不依赖于上次构建的缓存)。使用-f 指定Dockerfile文件的目录,并指定构建的上下文目录。github

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

构建镜像时,不经意包含没必要要的文件将会致使一个臃肿的构建上下文和一个臃肿的镜像,这将致使构建镜像时长增长,提交到仓库和从仓库拉取时长,容器运行时大小都将增长。构建镜像时能够看到构建上下文的大小。golang

经过.dockerignore文件排除

要排除与构建无关的文件(不重构源存储库),请使用.dockerignore文件。此文件支持相似于.gitignore文件的排除模式web

使用多级构建

多级构建容许你大幅减少最终图像的大小而无需努力减小中间层和文件的数量。因为图像是在构建过程的最后阶段构建的,所以能够经过利用构建缓存来最小化图像层。例如,若是您的构建包含多个图层,则能够从较不频繁更改(以确保构建缓存可重用)到更频繁更改的顺序对它们进行排序:sql

  • build程序所需的工具安装
  • 安装升级库依赖
  • 生产应用程序

示例: 一个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应用程序、数据库和内存缓存。

将每一个容器限制为一个进程是一个很好的经验法则,但它不是一个硬性规则。 例如,不只可使用init进程生成容器,并且某些程序可能会自行生成其余进程。 例如,Celery能够生成多个工做进程,Apache能够为每一个请求建立一个进程。

使用你最好的判断来保持容器尽量的干净和模块化。若是容器彼此依赖,可使用Docker容器网络来确保这些容器可以通讯。

减小图层的数量

在旧版本的Docker中,最大限度地减小图像中的图层数量以确保它们具备高性能很是重要。添加了如下特性来减小这种限制:

  • 只用RUN,COPY,ADD指令会建立层,其余指令建立临时中间层,并不增长构建的大小。
  • 在可能的状况下,使用多阶段构建,并仅将所需的工件复制到最终图像中。这容许您在中间构建阶段中包含工具和调试信息,而不会增长最终图像的大小

多行参数排序

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

示例:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion
复制代码

缓存利用

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

若是你不想使用缓存,能够在docker build命令中使用--no-cache=true选项,然而,若是你让Docker使用缓存,理解何时使用,何时不能,使用一个匹配的镜像将很是重要,Docker遵循的基本规则概述以下:

  • 从已在缓存中的父镜像开始,接下来的指令将对比全部的子镜像看是否有一个使用相同的指令构建而成,若是没有,缓存则是无效的。
  • 在大多数状况下,只需将Dockerfile中的指令与其中一个子映像进行比较就足够了。然而,某些指示须要更多的检查和解释。
  • 对于ADD和COPY指令,将检查映像中文件的内容,并为每一个文件计算校验和。 在这些校验和中不考虑文件的最后修改时间和最后访问时间。 在高速缓存查找期间,将校验和与现有映像中的校验和进行比较。 若是文件中的任何内容(例如内容和元数据)发生了任何更改,则缓存将失效。
  • 除了ADD和COPY命令以外,高速缓存检查不会查看容器中的文件以肯定高速缓存匹配。 例如,在处理RUN apt-get -y update命令时,不检查容器中更新的文件以肯定是否存在缓存命中。 在这种状况下,只需使用命令字符串自己来查找匹配项。

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

Dockerfile指令

FROM

FROM <image> [AS <name>]

或者

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

或者

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

  • FROM指令初始化新的构建阶段并为后续指令设置基本映像。所以,有效的Dockerfile必须以FROM指令开头.
  • FROM能够出现屡次在同一个Dockerfile文件中,为了建立多个镜像,或者使用一个做为另外一个镜像的依赖,只要在每一个新的FROM执行以前记录上一个镜像的ID。每一个FROM指令都会清空以前命令建立的任何状态。
  • 可选的,每一个FROM指令均可以经过AS name提供一个名词,该名词能够在子FROM指令和COPY --from<name|index>指令中指待该镜像。
  • tag和digest值是可选的,若是省略他们,将使用latest版本,若是未找到将会抛出异常。 尽量的使用官方镜像做为基础镜像。

了解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
复制代码

LABEL

你能够为你的镜像添加labels,用来组织镜像,记录证书信息,或者其余缘由,对应每一个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=""
复制代码

上面的也能够写成以下形式:

# 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"
复制代码

RUN

  • RUN
  • RUN ["executable", "param1", "param2"]

RUN指令将执行任何命令在当前镜像的一个新层上并提交结果。提交后的镜像将会在下一步中使用。

在使用反斜杠分隔的多行上拆分长或复杂的RUN语句,以使Dockerfile更具可读性,可理解性和可维护性。

RUN apt-get update && apt-get install -y这些命令不要分开,

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/*
复制代码

上面的这个指令是建议这样使用的,最后的执行用于清除安装过程当中的缓存。

CMD

CMD ["executable","param1","param2"]

CMD ["param1","param2"]

CMD command param1 param2

一个Dockerfile文件中,只能有一个CMD指令,若是出现多个,只有最后一个起做用。

CMD的主要目的是为执行容器提供默认值。 这些默认值能够包含可执行文件,也能够省略可执行文件,在这种状况下,您还必须指定ENTRYPOINT指令。

若是CMD用于为ENTRYPOINT指令提供默认参数,则应使用JSON数组格式指定CMD和ENTRYPOINT指令。exec表单被解析为JSON数组,这意味着您必须使用双引号(“)来围绕单词而不是单引号(')。

CMD指令应该被用于运行被镜像包含的软件,并使用提供的参数。CMD指令应尽量的使用CMD ["executable", "params1","params2"...].所以,若是镜像是一个服务,好比Apache,应这样运行CMD ["apache2", "-DFOREGROUND"].事实上,这种形式的指令被建议使用在以服务为基础的镜像上。

大多数状况,CMD应该提供一个交互式的shell。例如bash.python.perl.示例: CMD ["perl", "-de0"], CMD ["python"],或者CMD ["php" "-a"].以这种形式的指令,意味着当你执行如这样的命令时docker run -it python,你将进入一个可用的shell里面,CMD指令在和ENTRYPOINT指令结合使用时,将不多使用CMD ["param", "param"]这种形式,除非你和客户都很熟悉ENTRYPOINT是如何工做的。

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
复制代码

在这种状况下,若是将-P与docker run一块儿使用,则端口将为TCP公开一次,对UDP公开一次。 请记住,-P在主机上使用短暂的高阶主机端口,所以TCP和UDP的端口不一样。

不管EXPOSE设置如何,您均可以使用-p标志在运行时覆盖它们。 例如: docker run -p 80:80/tcp -p 80:80/udp ...

要在主机系统上设置端口重定向,请参阅使用-P标志。 docker network命令支持建立用于容器之间通讯的网络,而无需公开或发布特定端口,由于链接到网络的容器能够经过任何端口相互通讯。

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

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

ENV

ENV <key> <value>
ENV <key>=<value> ...
复制代码

ENV指令将环境变量设置为值。 此值将在构建阶段中的全部后续指令的环境中,而且也能够在许多内联替换。

ENV指令有两种形式。 第一种形式ENV ,将单个变量设置为一个值。 第一个空格后面的整个字符串将被视为 - 包括空格字符。 该值将针对其余环境变量进行解释,所以若是未对其进行转义,则将删除引号字符。

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

示例:

ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy
复制代码
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
复制代码

两种形式是同样的。

为了使新软件更易于运行,您可使用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 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命令同样。 这意味着即便您在未来的图层中取消设置环境变量,它仍然会在此图层中保留,而且能够转储其值。 您能够经过建立以下所示的Dockerfile来测试它,而后构建它。

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命令,在单个图层中设置,使用和取消设置变量all。 您能够将命令与; 要么 &&。 若是您使用第二种方法,而且其中一个命令失败,则docker构建也会失败。 这一般是一个好主意。 使用\做为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'不会输出mark

ADD or COPY

ADD

  • ADD [--chown=<user>:<group>] <src>... <dest>
  • ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

--chown功能仅用于Dockerfile构建linux容器.在windows容器上无效。

ADD指令从复制新文件,目录或远程文件URL,并将它们添加到路径的镜像文件系统中。 每一个可能包含通配符,匹配将使用Go的filepath.Match规则完成。 例如:

复制代码

是绝对路径,或相对于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将使用与GID相同的数字UID。 若是提供了用户名或组名,则容器的根文件系统/ 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不须要查找,也不依赖于容器根文件系统内容.

在是远程文件URL的状况下,目标将具备600的权限。若是正在检索的远程文件具备HTTP Last-Modified标头,则该标头的时间戳将用于设置目标上的mtime 文件。 可是,与ADD期间处理的任何其余文件同样,mtime将不包含在肯定文件是否已更改且应更新缓存中。

ADD遵照如下规则:

  • 路径必须位于构建的上下文中; 你不能添加../something / something,由于docker构建的第一步是将上下文目录(和子目录)发送到docker守护进程。
  • 若是是URL且不以尾部斜杠结尾,则从URL下载文件并将其复制到。
  • 若是是URL而且以尾部斜杠结尾,则从URL推断文件名,并将文件下载到<dest> /<filename>。 例如,ADD http://example.com/foobar /将建立文件/foobar。 URL必须具备很是重要的路径,以便在这种状况下能够发现适当的文件名(example.com将不起做用
  • 若是是目录,则复制目录的所有内容,包括文件系统元数据。不复制目录自己,只复制其内容。
  • 若是是可识别的压缩格式(identity,gzip,bzip2或xz)的本地tar存档,则将其解压缩为目录。 远程URL中的资源不会被解压缩。 复制或解压缩目录时,它与tar -x具备相同的行为,结果是:
1.不管在目的地路径上存在什么,
2.源树的内容,在逐个文件的基础上解决了有利于“2.”的冲突。
复制代码
  • 若是是任何其余类型的文件,则将其与元数据一块儿单独复制。 在这种状况下,若是以尾部斜杠/结尾,则将其视为目录,的内容将写入 / base()。
  • 若是直接或因为使用通配符指定了多个资源,则必须是目录,而且必须以斜杠/结尾。
  • 若是不以尾部斜杠结束,则将其视为常规文件,的内容将写入。
  • 若是不存在,则会在其路径中建立全部缺乏的目录。

COPY

  • COPY [--chown=<user>:<group>] <src>... <dest>
  • COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

--chown功能仅用于Dockerfile构建linux容器.在windows容器上无效。

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

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

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

COPY hom* /mydir/        # adds all files starting with "hom"
COPY hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"
复制代码

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

COPY test relativeDir/   # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/  # adds "test" to /absoluteDir/
复制代码

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

COPY arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/
复制代码

除非可选的--chown标志指定给定用户名,组名或UID / GID组合以请求复制内容的特定全部权,不然将使用UID和GID为0建立全部新文件和目录。 --chown标志的格式容许用户名和组名字符串或任意组合的直接整数UID和GID。 提供没有组名的用户名或没有GID的UID将使用与GID相同的数字UID。 若是提供了用户名或组名,则容器的根文件系统/ 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不须要查找,也不依赖于容器根文件系统内容。

可选地,COPY接受一个标志--from = <name | index>,可用于将源位置设置为先前的构建阶段(使用FROM .. AS 建立),而不是由发送的构建上下文 用户。 该标志还接受为使用FROM指令启动的全部先前构建阶段分配的数字索引。 若是找不到具备指定名称的构建阶段,则尝试使用具备相同名称的图像。

COPY遵照如下规则:

  • 路径必须位于构建的上下文中; 你不能COPY ../something / something,由于docker构建的第一步是将上下文目录(和子目录)发送到docker守护进程。
  • 若是是目录,则复制目录的所有内容,包括文件系统元数据。不复制目录自己,只复制其内容。
  • 若是是任何其余类型的文件,则将其与元数据一块儿单独复制。 在这种状况下,若是以尾部斜杠/结尾,则将其视为目录,的内容将写入 / base()。
  • 若是直接或因为使用通配符指定了多个资源,则必须是目录,而且必须以斜杠/结尾。
  • 若是不以尾部斜杠结束,则将其视为常规文件,的内容将写入。
  • 若是不存在,则会在其路径中建立全部缺乏的目录。

尽管ADD和COPY在功能上类似,但通常来讲,COPY是优选的。 那是由于它比ADD更透明。 COPY仅支持将本地文件基本复制到容器中,而ADD具备一些功能(如仅限本地的tar提取和远程URL支持),这些功能并非很明显。所以,ADD的最佳用途是将本地tar文件自动提取到图像中,如ADD rootfs.tar.xz /中所示.

若是在Dockerfile中有多个步骤使用不一样的文件,请针对每一个文件,单独执行COPY指令,而不是放在一块儿执行COPY,这确保了每一步构建的缓存在当前步骤使用,若是特定的文件改变了。 示例:

COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
复制代码

与放置在COPY以前相比,RUN步骤的缓存失效次数更少。 因为图像大小很重要,所以强烈建议不要使用ADD从远程URL获取包。 你应该使用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
复制代码

替代为:

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
复制代码

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

ENTRYPOINT

  • ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
  • ENTRYPOINT command param1 param2

ENTRYPOINT容许您配置将做为可执行文件运行的容器。 例如,如下将使用其默认内容启动nginx,侦听端口80: docker run -i -t --rm -p 80:80 nginx

exec形式的ENTRYPOINT中的命令行参数将会追加到docker run <image>全部元素的后面。而且会覆盖CMD指令指定的参数。这容许将参数传递给入口点。 docker run <image> -d会将-d参数传递给入口点。 您可使用docker run --entrypoint标志覆盖ENTRYPOINT指令。

shell表单能够防止使用任何CMD或run命令行参数,但缺点是ENTRYPOINT将做为/bin/sh -c的子命令启动,它不传递信号。 这意味着可执行文件不是容器的PID 1 - 而且不会收到Unix信号 - 所以您的可执行文件不会从docker stop <container>接收SIGTERM

Dockerfile文件中只有最后一个ENTRYPOINT指令会起做用.

示例

你可使用exec 形式的ENTRYPOINT来设置相对稳定的默认的命令和参数,并使用任意形式的CMD指令来设置额外的默认的可能会变化的。

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
复制代码

当你运行容器的时候,能够发现top是惟一的进程 docker run -it --rm --name test top -H

为了将来测试结果,你可以使用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
复制代码

而且您能够优雅地请求中止top使用docker stop test

下面的示例展现了使用ENTRYPOINT来运行Apache在前端

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"]
复制代码

示例2

可为ENTRYPOINT指定一个字符串,他将在/bin/sh -c中执行,这种形式使用shell处理替换环境变量,并会忽略CMD或者docker run 命令行参数。为了正确有效的执行docker stop,须要记住以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开头,以下:

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

复制代码

能够看到top输出代表ENTRYPOINT指令并未做为PID1的进程运行。 这个时候运行docker stop,容器不会正常退出,stop指令会强制发出一个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指令如何整合

这两个指令都定义了当运行一个容器的时候,应运行什么命令,在合同使用时需遵照如下规则:

  • Dockerfile应至少指定一个CMD或ENTRYPOINT命令。
  • 使用容器做为可执行文件时,应定义ENTRYPOINT。
  • CMD应该用做为ENTRYPOINT命令定义默认参数或在容器中执行ad-hoc命令的方法。
  • 使用备用参数运行容器时,将覆盖CMD。

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

null 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须要指定一个值在该镜像定义的时候。

ENTRYPOINT的最佳使用是用来设置镜像的主命令。容许该映像像该命令同样运行(而后使用CMD做为默认标志)。

让咱们以一个命令行工具s3cmd的镜像的例子开始:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]
复制代码

如今可使用下面的命令运行来展现命令行的帮助信息: docker run s3cmd

或者使用正确的参数来执行命令: docker run s3cmd ls s3://mybucket

VOLUME

VOLUME ["/data"]

VOLUME指令使用指定的名称建立一个挂载点,持有外部挂载的本地主机或者其余容器的卷。其值能够为一个JSON数组,VOLUMN ["/var/log"],或者一个字符串,例如VOLUMN /var/log或者VOLUMN /var/log /var/db.

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

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
复制代码

此Dockerfile会生成一个映像,该映像会致使docker run在/myvol上建立新的挂载点,并将greeting文件复制到新建立的卷中。

关于指定卷的说明: 特别注意如下几点关于Dockerfile文件中的卷

  • 基于windows容器的卷:当使用windows类型的容器时,容器中的卷需是一下中的一种:
1. 不存在或空目录
 2. C之外的驱动器
复制代码
  • 从Dockerfile中更改卷:若是任何构建步骤在声明后更改卷内的数据,那么这些更改将被丢弃。
  • JSON形式:该列表被解析为JSON数组。 您必须用双引号(“)而不是单引号(')括起来。
  • 主机目录在容器运行时声明:主机目录(mountpoint)本质上是依赖于主机的。 这是为了保持图像的可移植性,由于不能保证给定的主机目录在全部主机上均可用。 所以,您没法从Dockerfile中安装主机目录。 VOLUME指令不支持指定host-dir参数。 您必须在建立或运行容器时指定安装点。

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

USER

USER <user>[:<group>] or
USER <UID>[:<GID>]
复制代码

USER指令用于设置用户名或者UID,可选的用户组或者GID.当运行镜像的时候,以便在运行映像时以及Dockerfile中跟随它的任何RUN,CMD和ENTRYPOINT指令时使用。

在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中的任何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 为了清晰和可靠,您应该始终使用WORKDIR的绝对路径。 此外,您应该使用WORKDIR而不是像RUN CD ...&& do-something,这些指令难以阅读,故障排除和维护。

ONBUILD

在当前Dockerfile构建完成后执行ONBUILD命令。 ONBUILD在从当前图像派生的任何子图像中执行。 将ONBUILD命令视为父Dockerfile为子Dockerfile提供的指令。

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

ONBUILD对于将从给定图像构建的图像很是有用。 例如,您可使用ONBUILD做为语言堆栈映像,在Dockerfile中构建使用该语言编写的任意用户软件,如Ruby的ONBUILD变体中所示。

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

将ADD或COPY放入ONBUILD时要当心。 若是新构建的上下文缺乏正在添加的资源,则“onbuild”映像将发生灾难性故障。 如上所述,添加单独的标记有助于经过容许Dockerfile做者作出选择来缓解这种状况。

相关文章
相关标签/搜索