前言
Dockerfile官方文档:
Dockerfile reference | Docker Documentation
Dockerfile中文文档:
Docker中文文档 Dockerfile介绍-DockerInfo
一、dockerfile语法结构
1、Dockerfile介绍
我们可以把刚才的对容器的所有操作命令都记录到一个文件里,就像写更脚本程序。
之后用 docker build 命令以此文件为基础制作一个镜像,并会自动提交到本地仓库。
这样的话镜像的构建会变的透明化,对镜像的维护起来也更加简单,只修改这个文件即可。
同时分享也更加简单快捷,因为只要分享这个文件即可。
Dokcerfile 是一个普通的文本文件,文件名一般叫 Dockerfile。
其中包含了一系列的指令(Instruction), 每一条指令都会构建一层,就是描述该层是如何创建的。
2、Dockerfile基本结构
Dockerfile 由一行行命令语句组成,并且支持以
#
开头的注释行。
一般的,Dockerfile 分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。例如:
# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
# Base image to use, this must be set as the first line
#指明基础镜像名称
FROM ubuntu
# Maintainer: docker_user <docker_user at email.com> (@docker_user)
#说明维护者信息
MAINTAINER docker_user docker_user@email.com
# Commands to update the image
#RUN指令,对镜像执行
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# Commands when creating a new container
#CMD指令,对容器执行
CMD /usr/sbin/nginx
Dockerfile构建完成后。使用docker build进行镜像构建(不要忘记最后面的点”.”):
docker bulid -t 仓库名/镜像名:tag .
如上命令格式,其中“.”点表示当前目录,-t参数表示构建后的镜像的自定义名称,该命令会自动读取当前目录下名为Dockerfile的文件,当然Dockerfile不一定非得叫Dockerfile,名称可以自定义,只不过如果不指定,默认读取的是前目录下名为Dockerfile的文件,如果写成其他名称,比如myDockerfile,则需要使用-f参数指定文件名称,如下:
docker build -f /path/myDockerfile -t 仓库名/镜像名:tag .
二、Dockerfile使用
1、Dockerfile常用参数指令
(1)
FROM
格式为 FROM <image>或FROM <image>:<tag>
第一条指令必须为 FROM 指令
。并且,如果在同一个Dockerfile中创建多个镜像时,可以使用多个 FROM 指令(
每个镜像一次
)。
【实例】:
FROM redis
……
FROM nginx
…….
(2)
MAINTAINER
格式为 MAINTAINER <name>,指定维护者信息
【实例】:
MAINTAINER Victor Vieux <victor@docker.com>
(3)
RUN
格式为 RUN <command> 或 RUN [“executable”, “param1”, “param2”]。
前者将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如 RUN [“/bin/bash”, “-c”, “echo hello”]。
每条 RUN 指令将
在当前镜像基础上执行
指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。
【实例】:
RUN [“/bin/bash”, “-c”, “echo hello”]
RUN /bin/bash -c ‘apt-get update && apt-get -y install vim’
RUN set -evx -o pipefail \
&& apk update \
&& apk add –no-cache apache2-utils \
&& rm -rf /var/cache/apk/* \
&& ab -V |grep Version
(4)
CMD
支持三种格式:
CMD [“executable”,”param1″,”param2″] 使用 exec 执行,
推荐方式
;
CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
CMD [“param1″,”param2”] 提供给 ENTRYPOINT 的默认参数;
指定
启动容器时执行
的命令,
每个 Dockerfile 只能有一条 CMD 命令
。如果指定了多条命令,只有最后一条会被执行。
如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令
。
【实例】:
CMD [“/bin/echo”, “Hello” , “World”] #注意使用双引号
CMD ps -aux | wc -l #如果参数不是字符串,就不能使用引号
CMD echo “hahaha”
【注】:RUN指令和CMD指令都可以执行命令,区别是RUN是在构建镜像的时候执行,CMD是在构建容器的时候执行
(5)
EXPOSE
格式为 EXPOSE <port> [<port>…]
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配一个端口转发到指定的端口。
【实例】:
#曝露多个端口
EXPOSE 8080
EXPOSE 22
EXPOSE 8009
EXPOSE 80/tcp
EXPOSE 80/udp
【注】:如果在docker run的时候使用-P或-p指定了端口,那么会将Dockerfile里面EXPOSE的端口
覆盖
掉(全部覆盖),所以EXPOSE使用的不是很多,可以如下在docker run的时候指定:
docker run -p 80:80/tcp -p 80:80/udp ….
(6)
ENV
格式为 ENV <key> <value>或者ENV <key>=<value> 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。
【实例】:
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命令查看
(7)
COPY
作用:复制内容到镜像
格式: COPY <src> <dest>
详解:复制本地主机的 <src>下内容到镜像中的 <dest>,目标路径不存在时,会自动创建。
<src>:可以是 Dockerfile 所在目录的一个相对路径(文件或目录)
<dest>:可以是镜像内绝对路径,或者相对于工作目录(WORKDIR)的相对路径,
<dest>需为绝对路径或相对于 WORKDIR 的路径
。
<dest>路径最后面加不加斜杠/区别很大
,只有最后有斜杠,才会视为该路径为目录,如果没有斜杠,只会视为该路径为一个普通文件。
路径:支持正则表达式, COPY test* /tmp
当使用本地目录为源目录时,推荐使用 COPY
。
支持以下两种格式:
COPY [–chown=<user>:<group>] <src>… <dest>
COPY [–chown=<user>:<group>] [“<src>”,… “<dest>”]
【实例】:
#把所有 hom 开头的文件复制到镜像文件系统的 /mydir/ 目录下
COPY hom* /mydir/
#? 匹配 0 或 1 个字符,比如会把 home.txt 文件复制到 /mydir/ 目录下
COPY hom?.txt /mydir/
#包含特殊字符,如添加名为 arr[0].txt 的文件
COPY arr[[]0].txt /mydir/
#使用相对路径,需提前定义WORKDIR
WORKDIR /data
copy test.txt TeamFile/
#不支持../something写法,如下错误写法
COPY ../test.txt /mkdir/
#src是目录,会复制目录的全部内容,包含文件系统元数据
#但是不会复制目录本身,只会复制其内容
COPY dir /mydir/
#src包含多个资源
COPY test1.txt test2.txt /mydir/
#dest不以斜杠结尾,它将被视为常规文件,并且 <src> 的内容将写入 <dest>
COPY test.txt /mytext
#dest不存在,路径中所有缺失的目录都会自动创建
COPY test.txt /dir/test/my/
#添加目录
COPY TeamFile /
【注意】:ADD和COPY的区别和使用场景
a、ADD 支持添加远程 url 和自动提取压缩格式的文件,COPY 只允许从本机中复制文件
b、COPY 支持从其他构建阶段中复制源文件(–from)
c、根据官方 Dockerfile 最佳实践,
除非真的需要从远程 url 添加文件或自动提取压缩文件才用 ADD,其他情况一律使用 COPY
d、ADD 从远程 url 获取文件和复制的效果并不理想,因为该文件会增加 Docker Image 最终的大小
(8)
ADD
格式为 ADD <src> <dest>
该命令将复制指定的 <src> 到容器中的 <dest>。 其中 <src> 可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。
<dest>需为绝对路径或相对于 WORKDIR 的路径
。
<dest>路径最后面加不加斜杠/区别很大
,只有最后有斜杠,才会视为该路径为目录,如果没有斜杠,只会视为该路径为一个普通文件。
支持两种格式:
ADD [–chown=<user>:<group>] <src>… <dest>
ADD [–chown=<user>:<group>] [“<src>”,… “<dest>”]
ADD 作用:
ADD 指令从 <src> 复制新文件、目录或远程文件 URL,并将它们添加到路径 <dest>
可以指定多个 <src> 资源,但如果它们是文件或目录,则它们的路径被解析为相对于构建上下文的源
每个 <src> 可能包含通配符,匹配将使用 Go 的 filepath.Match 规则完成
【实例】:
# 把所有 hom 开头的文件添加到镜像文件系统的 /mydir/ 目录下
ADD hom* /mydir/
# ? 匹配 0 或 1 个字符,比如会把 home.txt 文件添加到 /mydir/ 目录下
ADD hom?.txt /mydir/
#src是一个URL
ADD http://example.com/foobar /mydir/
#src是目录,会复制目录的全部内容,包含文件系统元数据
#但是不会复制目录本身,只会复制其内容
ADD dir /mydir/
#src是压缩格式(gzip、bzip2、identity、xz)的本地tar文件
#会将其自动解压为目录
#但来自远程 URL 资源不会被解压缩
#文件是否被识别为可识别的压缩格式完全取决于文件的内容,而不是文件的名称
ADD log.zip /mydir/
#src包含多个资源
ADD test1.txt test2.txt /mydir/
#dest不以斜杠结尾,它将被视为常规文件,并且 <src> 的内容将写入 <dest>
ADD test.txt /mytext
#dest不存在,路径中所有缺失的目录都会自动创建
ADD test.txt /dir/test/my/
#添加目录
ADD TeamFile /
#使用相对路径,要先定义WORKDIR
WORKDIR /data
ADD test.txt mydir/
【注意】:ADD和COPY的区别和使用场景
a、ADD 支持添加远程 url 和自动提取压缩格式的文件,COPY 只允许从本机中复制文件
b、COPY 支持从其他构建阶段中复制源文件(–from)
c、根据官方 Dockerfile 最佳实践,
除非真的需要从远程 url 添加文件或自动提取压缩文件才用 ADD,其他情况一律使用 COPY
d、ADD 从远程 url 获取文件和复制的效果并不理想,因为该文件会增加 Docker Image 最终的大小
(9)
ENTRYPOINT
两种格式:
ENTRYPOINT [“executable”, “param1”, “param2”]
ENTRYPOINT command param1 param2(shell中执行)。
配置
容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖
。
每个 Dockerfile 中
只能有一个 ENTRYPOINT
,当指定多个时,只有最后一个起效。
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令。
那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?这种 <ENTRYPOINT> “<CMD>” 有什么好处么?
CMD和ENTRYPOINT区别
CMD # 指定这个容器启动的时候要运行的命令,
不可以追加命令,
docker run的时候如果追加了执行命令,
会覆盖
CMD的命令
ENTRYPOINT # 指定这个容器启动的时候要运行的命令,
可以追加命令,
docker run的时候如果追加了执行命令(不会被执行,只会作为ENTRYPOINT的一个传参参数),
不会覆盖
ENTRYPOINT的命令,
除非在docker run的时候添加–entrypoint
会覆盖dockerfile里的ENTRYPOINT
【实例】:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*ENTRYPOINT [ “curl”, “-s”, “http://myip.ipip.net” ]
docker run myip -i #后面的-i会传给ENTRYPOINT,即变成curl -s http://myip.ipip.net -i
还可以调用一个脚本,并传入参数,dockerfile如下:
FROM alpine:3.4
…
RUN addgroup -S redis && adduser -S -G redis redis
…
ENTRYPOINT [“docker-entrypoint.sh”]EXPOSE 6379
CMD [ “redis-server” ]
docker-entrypoint.sh内容如下:
#!/bin/sh
…
# allow the container to be started with `–user`
if [ “$1” = ‘redis-server’ -a “$(id -u)” = ‘0’ ]; then
find . \! -user redis -exec chown redis ‘{}’ +
exec gosu redis “$0” “$@”
fiexec “$@”
docker build执行命令如下:
docker run -it redis id # id是传参,获取当前用户的命令
参考链接:
ENTRYPOINT 入口点 – Docker — 从入门到实践 (gitbook.io)
CMD和ENTRYPOINT配合使用:
FROM nginx
CMD /root #单独执行等价于ls /root
此时如果使用docker run mynginx_cmd /etc,还是只是会ls /root,并不会追加显示/etc下的内容
如果改成如下:
FROM nginx
ENTRYPOINT [“ls”]
此时如果使用docker run mynginx_cmd /etc,会追加显示/etc下的内容,即可以追加参数
(10)
VOLUME
格式为 VOLUME [“/data”]。
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
【实例】:
#支持json格式
VOLUME [“/usr/local/data1”] #创建一个挂载点,这里是容器的目录,宿主机的挂载目录会自动生成
VOLUME [“/data1″,”/data2”] #创建2个挂载点
#也可以简写
VOLUME /data1
#挂载情况可以使用docker volume ls查看
【注】:dockerfile的VOLUME指令和docker -v都可以实现数据卷的挂载,区别是docker -v可以指定宿主机的挂载目录,而Dockerfile的VOLUME是挂载到宿主机上的匿名卷,是在/var/lib/docker/volumes下自动随机生成一个数据卷
(11)
USER
格式为 USER daemon。
指定运行容器时的用户名或 UID
,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres。要临时获取管理员权限可以使用 gosu,而不推荐 sudo。
【实例】:
#实例1
FROM microsoft/windowsservercore
# 在容器创建新用户
RUN net user /add patrick# 设置用户
USER patrick
#实例2:
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ “redis-server” ]
#实例3:
#如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来
#运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺
#失的环境下经常出错。
建议使用 gosu
。# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu “https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64” \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ “exec”, “gosu”, “redis”, “redis-server” ]
也可以使用docker run -u 指定用户:
docker run -i -t -u 1001 busybox sh
(12)
WORKDIR
格式为 WORKDIR /path/to/workdir
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。
可以使用多个
WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
【实例】:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd#则最终路径为 /a/b/c
也可以结合ENV变量使用:
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd # /path/$DIRNAME,直接切换目录路径,也会改变进入容器时候的初始路径
(13)
ONBUILD
格式为 ONBUILD [INSTRUCTION]。
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
当我们编写一个新的Dockerfile文件来基于A镜像构建一个镜像(比如为B镜像)时,这时构造A镜像的Dockerfile文件中的ONBUILD指令就生效了,在构建B镜像的过程中,首先会执行ONBUILD指令指定的指令,然后才会执行其它指令。
需要注意的是,如果是再利用B镜像构造新的镜像时,那个ONBUILD指令就无效了,也就是说只能再构建子镜像中执行,
对孙子镜像构建无效
。其实想想是合理的,因为在构建子镜像中已经执行了,如果孙子镜像构建还要执行,相当于重复执行,这就有问题了。
利用ONBUILD指令,
实际上就是相当于创建一个模板镜像
,后续可以根据该模板镜像创建特定的子镜像,需要在子镜像构建过程中执行的一些通用操作就可以在模板镜像对应的dockerfile文件中用ONBUILD指令指定。 从而减少dockerfile文件的重复内容编写。
【实例】:
例如,Dockerfile 使用如下的内容创建了镜像 image-A。
[…]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build –dir /app/src
[…]
如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build –dir /app/src
三、docker build使用
1、docker build语法
典型语法:
(最后的ContextPath通常用点”.”表示,其可以释义为:指定镜像构建过程中的上下文环境的目录)
docker build -f [Dockerfile_path] -t image_name:tag [ContextPath]
官方通用语法:
docker build [OPTIONS] PATH | URL | –
【OPTIONS说明】:
-
–build-arg=[] :
设置镜像创建时的变量; -
–cpu-shares :
设置 cpu 使用权重; -
–cpu-period :
限制 CPU CFS周期; -
–cpu-quota :
限制 CPU CFS配额; -
–cpuset-cpus :
指定使用的CPU id; -
–cpuset-mems :
指定使用的内存 id; -
–disable-content-trust :
忽略校验,默认开启; -
-f :
指定要使用的Dockerfile路径; -
–force-rm :
设置镜像过程中删除中间容器; -
–isolation :
使用容器隔离技术; -
–label=[] :
设置镜像使用的元数据; -
-m :
设置内存最大值; -
–memory-swap :
设置Swap的最大值为内存+swap,”-1″表示不限swap; -
–no-cache :
创建镜像的过程不使用缓存; -
–pull :
尝试去更新镜像的新版本; -
–quiet, -q :
安静模式,成功后只输出镜像 ID; -
–rm :
设置镜像成功后删除中间容器; -
–shm-size :
设置/dev/shm的大小,默认值是64M; -
–ulimit :
Ulimit配置。 -
–squash :
将 Dockerfile 中所有的操作压缩为一层。 -
–tag, -t:
镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。 -
–network:
默认 default。在构建期间设置RUN指令的网络模式
2、关于docker build最后面的点”.”的释义说明
很多讲解说最后面的点”.”表示Dockerfile的路径,其实实际操作过程中验证不是这样,-f才是表示Dockerfile路径,如果用了-f指定路径,最后不加点”.”,build语句会报错(”docker build” requires exactly 1 argument.),那么 . 号究竟是用来做什么的呢?
这里就有了一个镜像构建上下文的概念,当构建的时候,由用户指定构建镜像的上下文路径,而 docker build 会将这个路径下所有的文件都打包上传给 Docker 引擎,引擎内将这些内容展开后,就能获取到所有指定上下文中的文件了。
比如说 dockerfile 中的 COPY ./package.json /project,其实拷贝的并不是本机目录下的 package.json 文件,而是 docker引擎 中展开的构建上下文中的文件,所以如果拷贝的文件超出了构建上下文的范围,Docker引擎 是找不到那些文件的。
所以 docker build 最后的 . 号,其实是在指定镜像构建过程中的上下文环境的目录。
比如如果在宿主机的根目录/下执行docker build . 进行build,则会将宿主机的根目录下的所有文件(包含子目录)都作为一个上下文环境目录发送给docker引擎,这样构建出来的镜像就会很大,将宿主机的根目录下所有内容都打包进去,实际上在根目录下build会报错,作为docker的保护机制。所以我们docker build一般是在一个干净目录环境,该目录下只放置build image需要的文件,再构建镜像。
3、实例
a、使用当前目录的 Dockerfile 创建镜像,标签为 runoob/ubuntu:v1
docker build -t runoob/ubuntu:v1 .
b、使用URL
github.com/creack/docker-firefox
的 Dockerfile 创建镜像
docker build github.com/creack/docker-firefox
c、也可以通过 -f Dockerfile 文件的位置
docker build -f /path/to/a/Dockerfile .