白驹过隙,这篇文章距今已有一年以上的历史。技术发展日新月异,文中的观点或代码很可能过时或失效,请自行甄别:)

简介

dockerfile是专门用来构建镜像的文件,使用docker build命令来进行镜像的构建。

通常为docker build -t ${imageName}:${tagName} .,指使用当前目录下的Dockerfile文件来进行镜像构建。如果需要指定具体某个Dockerfile文件,可以使用-f参数,例如docker build -t ${imageName}:${tagName} -f /path/to/dockerfile .来进行构建。

Dockerfile关键字/语法

一个常见的Dockerfile一般为这个:

FROM ubuntu:16.04

COPY ./a.txt /tmp

下面一一来说明下常用的一些指令。

FROM

FROM指的是这个镜像要从哪个镜像进行构建,语法为:

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

如果image在本地不存在,会从远程仓库中先pull在执行

RUN

RUN会执行相应的命令,同时会在当前docker的镜像中写入一个新的layer层并提交。,基本语法有:

RUN <command>
RUN ["executable","param1","param2"]

推荐使用第二种,同时注意:

  1. 要使用双引号而不是单引号,如果在第一种写法中有换行需求,使用反斜杠\来执行
  2. 如果我们要使用反斜杠来代表反斜杠时,注意要写两个\\来代表
  3. Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen。For example, RUN [ "echo", "$HOME" ] will not do variable substitution on $HOME。
  4. RUN执行会再下次执行build时被缓存,如果不想要缓存而是真正从头构建,那么构建时需要添加--no-cache标识

CMD

官网对CMD的主要用途的说明是:用来在执行一个容器时提供默认值。因此,每个Dockerfile文件中只能有一个CMD指令。

CMD有3种写法

CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)

注意第一种是最佳实践的写法,同时基本上CMD和ENTRYPOINT配合使用的。

注意:

  1. 如果你需要你的command不是在shell下执行,那么你必须使用第一种写法,同时给你的二进制文件指定成绝对路径

LABEL

LABEL用来给镜像添加一些元数据,一个LABEL执行是一个key-value键值对。基本语法如下:

LABEL <key>=<value> <key>=<value> <key>=<value>...

MAINTAINER

MAINTAINER指令用来在生成的镜像中显示作者是谁。基本语法如下:

MAINTAINER <name>

不过现在看官方文档说这个指令即将被废弃了,推荐用LABEL标签来替代这个功能,例如:LABEL maintainer="SvenDowideit@home.org.au"

EXPOSE

EXPOSE指令指定了一个容器在运行时指定要监听的指定类型的网络端口。可以指定TCP和UDP类型,默认是TCP。语法如下:

EXPORT <port> [<port>/<protocol>...]

不过需要注意的是,EXPOSE并不会真正的暴露这些端口出来,他只是一种在镜像的构建者和使用者之间的文档说明功能,告诉哪些端口会暴露。当运行一个容器时,在执行docker run命令时,需要使用-p或者-P标签来暴露端口

ENV

ENV指令用来设置环境变量,具体的语法为:

ENV <key> <value>
ENV <key>=<value> ...

第一种为设置一个环境变量,第二种为在一行内设置多个环境变量,这些环境变量会在后续的Dockerfile中被用到,不过需要注意的是,将环境变量持久化可能会造成一些不期望的副作用,如果要在某个命令中使用某个环境变量,可以使用RUN <key>=<value> <command>

ADD

ADD指令用来将宿主机上的文件,目录或者远程地址的文件复制到镜像中,有两种语法:

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

注意:

  1. src支持通配符,例如ADD home* /mydir/会将home开头的所有文件复制到镜像的/mydir/文件下
  2. dest必须为绝对地址,如果设置为相对地址时,那么是相对于WORKDIR
  3. --chown只能是Linux容器使用,windows下无法使用
  4. src的路径如果是本地,那么必须是在当前执行docker build的上下文环境也就是同一个目录下的,不能执行../../这种路径
  5. 如果src是一个目录,那么目录下的所有文件都会被复制,包括文件的元数据,不过注意,目录本身不会被复制
  6. 如果scr是一个压缩文件,那么ADD操作会默认直接进行解压成对应的目录,但是如果是一个远程地址不会执行解压操作
  7. ADD指令在一个Dockerfile找那个支持执行多次

COPY

COPY指令用来复制文件或者目录到相应的路径,基本的语法是:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] (this form is required for paths containing whitespace)

COPY指令和ADD差不多,只是不同的是如果src是一个压缩文件,并不会和ADD一样执行解压操作

ENTRYPOINT

ENTRYPOINT指令用来当容器启动的时候执行什么命令,基本的语法为:

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

其实官网文档说的蛮清楚,懒得翻译了,直接贴过来吧。

Command line arguments to docker run will be appended after all elements in an exec form ENTRYPOINT, and will override all elements specified using CMD. This allows arguments to be passed to the entry point, i.e., docker run -d will pass the -d argument to the entry point. You can override the ENTRYPOINT instruction using the docker run --entrypoint flag.

有一点值得注意的是,虽然CMD和RUN指令也能和ENTRYPOINT一样做同样的事情,但是启动的时候会作为/bin/sh -c的子命令,进程的PID不是1,会导致执行docker stop时收不到unix的信号量例如SIGTERM

注意,如果你写了多个ENTRYPOINT,只有最后一个会生效。

最后,基本都是entrypoint配合CMD来一起使用的,这样方便容器的使用者通过传参进来后覆写CMD指令,所以一般都是CMD用来做程序的参数,ENTRYPOINT执行相应的程序,例如:

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

关于具体如何使用ENTRYPOINT和CMD来配合,官方文档说的蛮清楚,详见https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact

VOLUME

VOLUME指令创建一个指定名称以及从宿主机或者其他容器进行挂载的挂载点。基本的语法如下:

VOLUME ["/data"]
VOLUME /var/log /var/db

关于Docker中的volume,可以看下官方文档对volume的一些说明:

  1. Share Directories via Volumes
  2. VOLUME instruction

USER

USER指令用来在运行一个镜像或者是在接下来的Dockerfile中执行任何RUN,CMD,ENTRYPOINT时设置具体的用户名或者UID,或者同时再指定用户组或者GID

注意,如果user没有一个主要的组将会在root用户组下运行

WORKDIR

WORKDIR指令是设置在执行接下来的RUN,CMD,ENTRYPONT,COPY,ADD指令时的工作目录。如果在Dockerfile中没有设置WORKDIR,那么WORKDIR将会自动创建.

有这些需要注意的:

  1. WORKDIR指令可以在Dockerfile中多次使用,只是如果第一次是绝对路径而后面有相对路径,那么这个相对路径是基于上面的绝对路径的,例如:

    WORKDIR /a
    WORKDIR b
    WORKDIR c
    RUN pwd

    这里,pwd的执行路径是/a/b/c

  2. WORKDIR可以使用Dockerfile中设置的环境变量,例如:

    ENV DIRPATH /path
    WORKDIR $DIRPATH/$DIRNAME
    RUN pwd

    pwd的执行路径为/path/$DIRNAME

ARG

ARG指令是定义一个变量,同时这个变量的时在执行docker build时通过--build-arg <varname>=<value>标识传递进来。如果定义了一个变量但是用build的时候没有传递,将会收到如下警告:

[Warning] One or more build-args [foo] were not consumed.

基本的语法如下:

ARG <name>[=<default value>]

Dockerfile可以包括多个ARG指令。如果设置了默认值,用户在build时没有传递将会用默认值代替。同时需要注意的是ARG生效的位置是从被定义的位置开始,而不是从被使用的那行开始。看下面这个例子:

FROM busybox
USER ${user:-some_user}
ARG user
USER $user

用户通过下列命令来进行构建

docker build --build-arg user=what_user .

第二行中USER因为默认值的原因值为some_user,然而在第三行user变量才被定义。第四行中,定义的user变量被通过构建时传进来的what_user赋值。在被ARG定义之前,任何使用这个变量的值都为空字符串。

需要注意的是,ENV和ARG都可以做差不多类似的事情,只是ENV会在镜像中被持久化,而ARG只在构建过程中能够有效

ONBUILD

ONBUILD指令比较有趣,用来当构建出来的镜像被别人用做base镜像也就是FROM xxx时才会被触发。基本语法为:

ONBUILD [INSTRUCTION]

例如:

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

不过需要注意的是,ONBUILD的指令不能再次包含ONBUILD指令来递归操作。同时,ONBUILD只能可能不会触发FROM和MAINTAINER指令。

基本的指令和基本用法就这些了,其他的可以看官网文档

其他一些官网有用的资料:

  1. 官方具体的文档
  2. 官方出的dockerfile最佳实践