Dockerfile是一个文本文件,包含了一条条指令,每条指令对应构建一层镜像,Docker基于它来构建一个完整镜像。本文介绍Dockerfile的经常使用指令及相应的最佳实践建议。php
Docker镜像经过docker build
指令构建,该指令执行时当前的工做目录就是docker构建的上下文,即build context,上下文中的文件及目录都会做为构建上下文内容发送给Docker Daemon。python
docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context |
如上 –no-cache 表示镜像构建时不使用缓存,-f 指定Dockerfile文件位置, context 指定build context目录。mysql
将一些非必要的文件包含到build context中,会致使build context过大,从而致使镜像过大,会增长镜像构建、推送及拉取的时间,以及容器运行时的大小。nginx
执行docker build时会显示build context的大小,git
Sending build context to Docker daemon 187.8MB |
最佳实践建议github
使用.dockerignore来排除不须要加入到build context中的文件,相似于.gitignoreweb
不要安装没必要要的包,全部包含的东西都是镜像必须的,非必须的不要包含。redis
解耦应用,若是应用有分层,解耦应用到多个容器,便于横向扩展,如web应用程序栈包含web服务应用,数据库,缓存等。sql
最少化镜像层数:只有RUN、COPY、ADD指令会建立镜像层,其它指令建立临时的中间镜像,不会增大镜像构建的大小docker
若是可能,尽量使用多阶段构建,只复制你须要的组件到最终镜像,这使得你能够在中间构建阶段包含工具与debug信息,同时又不会增大最终镜像的大小。
排序多行参数:将参数按字母排序,有利于避免包重复,及后续的维护与提升易读性
做用
FROM指定基础镜像,每个定制镜像,必须以一个现有镜像为基础。所以一个Dockerfile中FROM是必须的指令,而且必须是第一条。使用格式,
FROM <image>:<tag> |
最佳实践建议
若是不想以任何镜像为基础,则可使用FROM scratch
尽可能使用官方镜像做为基础镜像
推荐使用Alpine镜像,由于它足够轻量级(小于5MB),但麻雀虽小五脏俱全,基本具备Linux的基础功能
做用
用来执行命令行命令,是最经常使用的指令之一。使用格式,
# shell格式,跟直接在命令行输入命令一行 |
RUN指令建立的中间镜像会被缓存,并会在下次构建中使用。若是不想使用这些缓存镜像,能够在构建指令中指定–no-cache参数,如:docker build --no-cache
最佳实践建议
将比较长的复杂的指令经过 \ 分为多行,让Dockerfile文件可读性、可理解性、可维护性更高,将多个指令经过 && 链接,减小镜像的层数
确保每一层只添加必需的东西,任何无关的东西都应该清理掉,如全部下载、展开的文件,apt 缓存文件等,以尽量减小镜像各层的大小
将RUN apt-get update
与 RUN apt-get install
组合成一条RUN指令(将apt-get update单独做为一条指令会由于缓存问题致使后续的apt-get install 指令失败)
好比先按以下Dockerfile建立了一个镜像
FROM ubuntu:18.04 |
一段时间后,再按如下Dockerfile建立另外一个镜像
FROM ubuntu:18.04 |
由于RUN指令建立的镜像层会被缓存,因此下面镜像的RUN apt-get update
并不会执行,直接使用了前面构建的镜像层,这样,curl、nginx就可能安装已通过时的版本。
所以 在 apt-get update
以后当即接 && apt-get install -y
,这叫作“ cache busting”(缓存破坏),也能够经过指定包的版本,来达到一样的目的,这叫“ version pinning” (版本指定)示例:
RUN apt-get update && apt-get install -y \ |
使用管道(pipes)。一些RUN指令依赖于从一个指令管道输出到另外一个,如
RUN wget -O - https://some.site | wc -l > /number |
Docker使用/bin/sh -c 解释器来执行这些指令,只会评估管道最后一条命令的退出码来肯定是否成功,如上例中只要wc -l成功了就算wget失败,也会认为是成功的。
若是要使管道命令的任何一步报错都致使指令失败,则可经过加 set -o pipefile &&
来实现,如
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number |
不是全部的shell都支持-o pipefail
选项,若是不支持的话可使用以下形式,显式地指定一个支持的shell
RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"] |
做用
COPY从构建上下文的目录中复制文件/目录到镜像层的目标路径。使用格式,
COPY [--chown=<user>:<group>] <源路径>... <目标路径> |
同RUN同样,也有两种格式。源文件能够多个,甚至能够是通配符,目标路径是容器的绝对路径,能够是相对工做目录(WORKDIR指定)的相对路径,目标路径不存在时会自动建立。使用--chown=<user>:<group>
来改变文件的所属用户与组。
ADD与COPY的使用格式与性质差很少,但功能更丰富,如源路径能够是URL(下载后放到目标路径下,文件权限为600),也能够为tar压缩包,压缩格式为gzip,bzip2及xz的状况下,ADD 指令将会自动解压缩这个压缩文件到目标路径去
最佳实践建议
若是在Dockerfile中有多处须要使用不一样的文件,分别使用COPY,而不是一次性COPY全部的,这能够保证每一步的构建缓存只会在对应文件改变时,才会失效。好比
COPY requirements.txt /tmp/ |
若是把COPY . /tmp/
放在RUN上面,将使RUN层镜像缓存失效的场景更多——由于 . 目录(当前目录)中任何一个文件的改变都会致使缓存失效。
由于镜像大小的缘由, 使用ADD来获取远程包是很是不推荐的,应该使用curl或wget,这种方式能够在再也不须要使用时删除对应文件,而不须要增长额外的层,如,应避免以下用法
ADD http://example.com/big.tar.xz /usr/src/things/ |
而应使用
RUN mkdir -p /usr/src/things \ |
若是不须要使用ADD的自动解压特性,尽可能使用COPY(语义更清晰)
做用
CMD指定容器的启动命令。容器实质就是进程,进程就须要启动命令及参数,CMD指令就是用于指定默认的容器主进程的启动命令的。使用格式
# shell格式 |
在容器运行时能够指定新的命令来覆盖Dockerfile中设置的这个默认命令
最佳实践建议
服务类镜像建议:CMD ["apache2","-DFOREGROUND"]
,CMD ["nginx", "-g", "daemon off;"]
容器进程都应之前台运行,不能之后台服务的形式运行,不然启动就退出了。
其它镜像,建议给一个交互式的shell,如bash,python,perl等:CMD ["python"]
, CMD ["php", "-a"]
做用
ENTRYPOINT的目的和CMD同样,都是在指定容器启动时要运行的程序及参数。ENTRYPOINT在运行时也能够替代,不过比CMD要略显繁琐,须要经过docker run的参数 –entrypoint 来指定。若是指定了ENTRYPOINT,则CMD将只是提供参数,传递给ENTRYPOINT。使用ENTRYPOINT能够在容器运行时直接为默认启动程序添加参数。与RUN指令格式同样,ENTRYPOINT也分为exec格式和shell格式。
最佳实践建议
ENTRYPOINT可用来指定镜像的主命令,容许镜像能像命令同样运行,可使用CMD来做为默认的标志(参数),如
ENTRYPOINT ["s3cmd"] |
直接run时,至关于执行了s3cmd --help
。也可使用shell脚本,在脚本中作一些预处理的工做,如
COPY ./docker-entrypoint.sh / |
做用
为镜像添加label以方便组织镜像,记录licensce信息,帮助自动化实现等等。字符串中包含空格须要转义或包含在引号中, 如
# Set one or more individual labels |
做用
ENV设置环境变量,不管是后面的其它指令,如 RUN(使用 $环境变量key 的形式) ,仍是运行时的应用,均可以直接使用这里定义的环境变量。使用格式有两种,
#只能设置一个key value |
除了RUN,还有这些指令能够引用环境变量:ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、STOPSIGNAL 、 ONBUILD
最佳实践建议
定义环境变量,更新PATH环境变量,如要使 CMD [“nginx”] 运行,可设置环境变量 ENV PATH /usr/local/nginx/bin:$PATH
ENV也能够用于定义常量,便于维护
做用
ARG设置构建参数,即docker build命令时传入的参数。和ENV的效果差很少,都是设置环境变量,不一样的是,ARG设置的是构建环境的环境变量,在容器运行时是不会存在这些环境变量的。
Dockerfile中的ARG指令是定义参数名称,以及默认值(可选)。该默认值能够在执行构建命令docker build时用 –build-arg <参数名>=<值> 来覆盖。使用格式,
ARG <参数名>[=<默认值>] |
如 VOLUME /data
, 任何向/data目录写入的数据都会写入匿名卷。能够运行容器时覆盖这个挂载设置 docker run -d -v host-path:/data xxxx
最佳实践建议
VOLUME应该被用来暴露全部的数据存储,配置存储,或者被容器建立的文件、目录
若是数据动态变化,强烈建议使用VOLUME
做用
EXPOSE指令是声明运行时容器提供的服务端口,也只是一个声明,在容器运行时并不会由于这个声明应用就必定会开启这个端口的服务,容器启动时,仍是须要经过 -p host-port:container-port
来实现映射。EXPOSE主要是帮助镜像使用者了解这个镜像服务的监听端口,以方便进行映射配置,另外一个用处是在运行时若是是使用随机端口映射,也就是经过 docker run -P
的形式时,会自动随机映射EXPOSE声明的端口。使用格式,
EXPOSE <端口1> [<端口2>...] |
最佳实践建议
若是一个服务不须要权限也能运行,则使用USER来切换到非root用户,如RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
避免使用sudo,由于可能存在一些不可预见的TTY与信号转发行为致使问题,若是实在须要,考虑使用“gosu”。为了减小镜像层数,应避免不断切换USER
使用gosu示例
# 创建 redis 用户,并使用 gosu 换另外一个用户执行命令 |
做用
HEALTHCHECK用于检查容器的健康状态,Docker可经过健康状态来决定是否对容器进行从新调度。使用格式
HEALTHCHECK [选项] CMD <命令> |
支持的选项为
–interval=<间隔> :两次健康检查的间隔,默认为30秒
–timeout=<时长> :执行健康检查命令的超时时间,若是超时,则本次健康检查就被视为失败,默认30秒
–retries=<次数> :当连续失败指定的次数后,将容器状态置为unhealthy ,默认3次
命令的返回值决定了该次健康检查的成功与否—— 0 :成功;1 :失败;2 :保留(不要使用这个值),如:
FROM nginx |
它后面跟的是其它指令,好比 RUN , COPY 等,这些指令在当前镜像构建时并不会被执行。
ONBUILD命令在本镜像的子镜像中执行,把ONBUILD想象为父镜像为子镜像声明的一条指令,Docker会在子镜像全部命令以前执行ONBUILD指令。
最佳实践建议
当在ONBUILD指令中使用ADD或COPY时要注意,若是build context中没有指定的资源,可能致使灾难性的错误。
Docker在构建镜像时会复用缓存中已经存在的镜像,若是明确不使用缓存,则可加参数docker build --no-cache=true
使用缓存镜像的规则
从一个已存在于缓存的父镜像开始构建,则会将当前镜像的下一行指令与全部继承于那个父镜像的子镜像比较,若是其中没有一个是使用相同的指令构建的,则缓存失效
大部分状况下,将Dockerfile中的指令与其中一个子镜像简单比较就够了,可是某些指令须要更多的检查与说明:对于ADD,COPY指令,文件内容会被检查,会计算每个文件的checksum,checksum中不会考虑最后修改及最后访问时间,在缓存中查找时,checksum会与已经存在的镜像进行比较,若是文件中有修改,则缓存失效。除了ADD,COPY命令,缓存检查不会查看容器中的文件来决定缓存匹配,如处理RUN apt-get -y update
命令时,容器中文件的更新不会进行检查来肯定缓存是否命中, 这种状况下, 只会检查指令字符串自己是否匹配。
一旦缓存失效,全部后续的指令都会产生新的镜像,不会再使用缓存。
经过标准输入来生成Dockerfile构建,不会发送build context(从stdin读取build context,只包含Dockerfile),适用于一次性构建,不须要写Dockerfile
# 将会构建一个名称与tag均为none的镜像 |
连字符 - 做为文件名告诉Docker从stdin读取Dockerfile
使用stdin来生成Dockerfile, 可是使用当前目录做为build context
# build an image using the current directory as context, and a Dockerfile passed through stdin |
使用远程git仓库构建镜像,从stdin生成Dockerfile
docker build -t myimage:latest -f - https://github.com/docker-library/hello-world.git <<EOF |
END
相关阅读
Docker笔记(一):什么是DockerDocker笔记(二):Docker管理的对象
Docker笔记(三):Docker安装与配置
Docker笔记(四):Docker镜像管理
Docker笔记(五):整一个本身的镜像
Docker笔记(六):容器管理Docker笔记(七):经常使用服务安装——Nginx、MySql、Redis
Docker笔记(九):网络管理
Docker笔记(十):使用Docker来搭建一套ELK日志分析系统
做者:空山新雨
欢迎关注个人微信公众号:jboost-ksxy