博文大纲:

  • dockerfile基本结构
  • dockerfile常用指令
    • FROM——指定基础镜像
    • MAINTAINER——指定维护者信息
    • RUN——运行指令
    • COPY——复制文件\目录
    • ADD——更高级的复制文件\目录
    • ENV——设置环境变量
    • ARG——构建参数
    • EXPOSE——暴露端口
    • CMD——容器启动命令
    • ENTRYPOINT——入口点
    • ENTRYPOINT和CMD组合使用
    • VOLUME——定义匿名卷
    • USER——指定当前用户
    • WORKDIR——指定工作目录
    • ONBUILD——为他人做嫁衣

1、Dockerfile基本结构

Dockerfile由一行行命令语句组成,并且支持以#开头的注释行。

一般Dockerfile分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。如下:

# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: Ray
# Command format: Instruction [arguments / command] ..

# Maintainer: docker_user <docker_user at email.com> (@docker_user)
MAINTAINER Ray 916551518@qq.com

# Commands to update the image
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 /usr/sbin/nginx

其中,一开始必须指明所基于的镜像,接下来推荐说明维护者信息,再接下来就是镜像操作的指令,如RUN、COPY等。每运行一条指令,就会为镜像添加新的一层并提交,注:一个镜像最多不允许超过127层。最后时CMD指令,是指定运行容器时的操作指令。

2、Dockerfile中的指令

1)FROM——指定基础镜像

格式为:FROM <image>或者 FROM <image>:<tag>。

第一条指令必须为FROM指令,如果在同一个dockerfile中创建多个镜像时,可以使用多个FROM指令(每个镜像一次,但是一般不会这么做)。

2)MAINTAINER——指定维护者信息

格式为:MAINTAINER <name> <email>。用来指定维护者信息。

3)RUN——运行指令

格式为:RUN <command> 或者RUN ["executable", "param1", "param2"]。

前者将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如 RUN ["/bin/bash", "-c", "echo hello"]。

每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。(注:如果觉得镜像的层数可能过多,可以一个RUN指令后面接多条指令,中间使用&&进行拼接即可)。

4)COPY——复制文件\目录

格式为:格式为 COPY <src> <dest>。

作用:复制本地的<src> (源文件/目录必须要与Dockerfile在相同的目录中)到容器中的<dest>。

当使用本地目录为源目录时,推荐使用COPY。

使用COPY时,所指定的源文件/目录,也可以是其他镜像中的文件,格式如下:

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

5)ADD——更高级的复制文件\目录

格式为:ADD <src> <dest>。它和COPY很相似,同样需要源文件和Dockerfile位于相同目录中,或者是一个URL。它比COPY更为人性化些。

该命令将复制指定的 <src> 到容器中的 <dest>。 其中 <src> 可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL(自动下载URL所对应的文件);还可以是一个 tar 文件(自动解压为目录)。

在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。

ADD --chown=55:mygroup files* /mydir/
ADD --chown=bin files* /mydir/
ADD --chown=1 files* /mydir/

ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令了。

因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。

6)ENV——设置环境变量

格式为 ENV <key> <value>。 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。

举个栗子:

[root@master nginx]# cat Dockerfile 
# test
FROM nginx:latest
MAINTAINER Ray <916551517@qq.com>
ENV var1 hello world
ENV var2 test
RUN echo ${var1},${var2} > /test.txt
#最终此镜像运行的容器中test.txt文件内容如下:
root@262f47a7682a:/# cat test.txt 
hello world,test
#并且定义的变量存在该容器的环境变量中:
root@262f47a7682a:/# echo $var1
hello world
root@262f47a7682a:/# echo $var2
test

7)ARG——构建参数

格式:ARG <参数名>[=<默认值>]

构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。

Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。

在 1.13 之前的版本,要求 --build-arg 中的参数名,必须在 Dockerfile 中用 ARG 定义过了,换句话说,就是 --build-arg 指定的参数,必须在 Dockerfile 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的 Dockerfile 的时候比较有帮助,避免构建命令必须根据每个 Dockerfile 的内容修改。

8)EXPOSE——暴露端口

格式为:EXPOSE <port> [<port>...]。

该指令的作用是告诉docker服务端容器暴露的端口号,供互联系统使用,在启动容器时需要通过-P,docker主机会自动分配一个端口转发到指定的端口。

9)CMD——容器启动命令

它支持以下三种格式:

  • CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;
  • CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
  • CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;

作用是指定启动容器时执行的命令,每个dockerfile只有一条CMD命令,如果指定了多条,那么前面的会被覆盖,只有最后一条指令生效。

如果用户启动容器时指定了运行的命令,则会覆盖掉CMD指定的命令。

如下:

[root@master nginx]# cat Dockerfile    #dockerfile内容如下
# test
FROM nginx:latest
CMD echo hello world
CMD echo hello 
[root@master nginx]# docker run -t  ljz:v2    #只有最后一条CMD指令生效
hello
[root@master nginx]# docker run -t  ljz:v2 echo 123456    
#启动容器时又指定了其他指令,则会覆盖掉dockerfile中的所有指令
123456

一般我将CMD和ENTRYPOINT结合使用。也就是上面的第三种格式。

10)ENTRYPOINT——入口点

它支持下面两种格式:

  • ENTRYPOINT ["executable", "param1", "param2"];
  • ENTRYPOINT command param1 param2(shell中执行)。

配置容器启动后执行的命令,并且不可被docker run提供的参数覆盖。

每个dockerfile中只能有一个ENTRYPOINT ,当指定多个时,只有最后一个起效。

使用举例:

[root@master nginx]# cat Dockerfile        #dokerfile文件如下
# test
FROM nginx:latest
ENTRYPOINT echo hello world
ENTRYPOINT echo hello 
[root@master nginx]# docker run -t ljz:v3      #运行此镜像
hello
[root@master nginx]# docker run -t ljz:v3 echo 123456    #运行时指定的命令也不会被执行
hello
#但是可以通过“--entrypoint”指令将镜像中的ENTRYPOINT指令覆盖,只能是命令字
[root@master nginx]# docker run  --entrypoint hostname -t ljz:v3
afb421b81a7d

11)ENTRYPOINT和CMD组合使用

在某种情况下,ENTRYPOINT和CMD组合使用能发挥更大的作用。

组合使用ENTRYPOINT和CMD, ENTRYPOINT指定默认的运行命令, CMD指定默认的运行参数。

举个栗子:

[root@master nginx]# cat Dockerfile     #Dockerfile文件如下
# test
FROM centos:7
ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"]
[root@master nginx]# docker run -t ljz:v4     #运行容器
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.028 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.072 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.074 ms

查看容器最后一条执行的命令如下:

Dockerfile指令详解

上面执行的命令是ENTRYPOINT和CMD指令拼接而成. ENTRYPOINT和CMD同时存在时, docker把CMD的命令拼接到ENTRYPOINT命令之后, 拼接后的命令才是最终执行的命令. 但是由于上文说docker run命令行执行时, 可以覆盖CMD指令的值. 如果你希望这个docker镜像启动后不是ping localhost, 而是ping其他服务器,, 可以这样执行docker run:

Dockerfile指令详解

下表列出了如果把Shell表示法和Exec表示法混合, 最终得到的命令行, 可以看到如果有Shell表示法存在, 很难得到正确的效果:

Dockerfile    Command

ENTRYPOINT /bin/ping -c 3
CMD localhost    
#拼接后的指令如下:
 /bin/sh -c '/bin/ping -c 3' /bin/sh -c localhost

ENTRYPOINT ["/bin/ping","-c","3"]
CMD localhost               
#拼接后的指令如下:
/bin/ping -c 3 /bin/sh -c localhost

ENTRYPOINT /bin/ping -c 3
CMD ["localhost"]"         
#拼接后的指令如下:
/bin/sh -c '/bin/ping -c 3' localhost

ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"]            
#拼接后的指令如下:
/bin/ping -c 3 localhost

从上面看出, 只有ENTRYPOINT和CMD都用Exec表示法, 才能得到预期的效果。

12)VOLUME——定义匿名卷

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

指令格式为:VOLUME ["/data"]。

作用:/data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:

docker run -d -v mydata:/data xxxx

在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。

这种方式是docker manager volumes数据持久化方式,不支持Bind mount挂载方式(也就是不支持指定本地的目录)。

在基于镜像运行容器后,可以通过命令“docker inspect container_name”查看容器的详细信息,在返回的结果中,查看MOUNT字段可以看到容器内对应的本地目录位置,如下:

[root@master volumes]# docker inspect web02

返回的结果如下:

Dockerfile指令详解

13)USER——指定当前用户

命令格式为:USER <用户名>[:<用户组>]。

指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。

USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。

当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

如果以 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
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]

14)WORKDIR——指定工作目录

格式为:WORKDIR /path/to/workdir。

为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。

可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

则最终路径为 /a/b/c。

15)ONBUILD——为他人做嫁衣

格式为:ONBUILD [INSTRUCTION]。

配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。

例如,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

使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。

———————— 本文至此结束,感谢阅读 ————————