查看Dockerfile中可以使用的全部指令:<http://docs.docker.com/reference/builder >
制作Docker image 有两种方式:

  • 一是使用 Docker container,直接构建容器,再导出成 image 使用;
  • 二是使用 Dockerfile,将所有动作写在文件中,再 build 成 image。Dockerfile 的方式非常灵活,推荐使用。

Dockerfile 分为四部分:基础镜像信息维护者信息镜像操作指令容器启动时执行指令

FROM centos                                //FROM 基础镜像
MAINTAINER tianfeiyu                       //MAINTAINER 维护者信息
ENV PATH /usr/local/nginx/sbin:$PATH       //ENV 设置环境变量  ENV <key> <value>
ADD nginx-1.8.0.tar.gz /usr/local/         //ADD 文件放在当前目录下,拷过去会自动解压
ADD epel-release-latest-7.noarch.rpm /usr/local/  
RUN rpm -ivh /usr/local/epel-release-latest-7.noarch.rpm  //RUN 执行以下命令 
RUN yum install -y wget lftp gcc gcc-c++ make openssl-devel pcre-devel pcre && yum clean all
RUN useradd -s /sbin/nologin -M www
WORKDIR /usr/local/nginx-1.8.0              //WORKDIR 相当于cd
RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install
RUN echo "daemon off;" >> /etc/nginx.conf
EXPOSE 80                                    //EXPOSE 映射端口(暴露的端口)
CMD ["nginx"]                                //CMD 运行以下命令

>> 1、基础指令:FROM、MAINTAINER、RUN、EXPOSE

~ FROM

指定镜像继承自哪个基础镜像(基础镜像相当于java中的基础类),第一条非注释指令必须为 FROM 指令;通过from指令指定的镜像名必须是已经存在的镜像,后续指令都会基于这个镜像来执行;

  • 格式为 FROM <image> 或 FROM <image>:<tag>
~ MAINTAINER

指定维护者信息;

  • 格式为 MAINTAINER <name>
~ RUN

指定在镜像构建过程中要执行的命令行命令;

有2中格式:

  • shell格式:RUN <command> 将在 shell 终端中运行命令,就像直接在命令行中输入的命令一样;
    RUN echo 'zxj' > /usr/local/docker/tomcat/html/index.html等价于直接在终端输入echo命令;
  • exec格式:RUN ["executable", "param1", "param2"] 使用 exec 执行,更像是函数调用中的格式;指定使用其它终端可以通过第二种方式实现,
    例如 RUN [“/bin/bash”, “-c”,”echo hello”]
~ EXPOSE

指定容器要打开的端口,这个端口与宿主机的端口进行映射;(指定运行该镜像所使用的容器的端口,可以指定一个或多个端口,也可以使用多个这个命令);

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

Dockerfile如何去掉env变量 dockerfile env arg_Docker

>> 2、CMD、ENTRYPOINT

这两个指令用来指定在容器运行时运行的命令;

~ CMD

Docker 不是虚拟机,容器就是进程;既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数;CMD 指令就是用于指定默认的容器主进程的启动命令的;相当于cmd下输入Tomcat的start.bash;

指令用于指定一个容器的默认的可执行体,也就是容器启动后默认执行的命令;
若docker run没有指定任何执行命令,或Dockerfile里面没有ENTRYPOINT指令,那么就会使用CMD指定的默认的执行命令执行;否则就会使用docker run后面的命令或ENTRYPOINT指令的参数覆盖CMD参数;

  • 有点类似于RUN命令,只是RUN命令是指定镜像被构建时要运行的命令,而CMD是指定容器被启动时要运行的命令;
    这和使用docker run命令启动容器时指定要运行的命令很类似:docker run -i -t 仓库名 /bin/bash<=等价于=>CMD ["/bin/bash"];也可以为要运行的命令指定参数:CMD ["/bin/bash","-l"]
  • Dockerfile只能有一条CMD:如果指定了多条命令,只有最后一条会被执行;若想在启动容器时运行多个进程或多条命令,可以考虑使用类似Supervisor这样的服务管理工具;
  • docker run命令行覆盖CMD:如果用户启动容器(docker run)的时候指定了要运行的命令,则命令行中指定的命令会覆盖掉在Dockerfile里 CMD 指定的命令;
  • 支持三种格式:
  • CMD ["executable","param1","param2"] :要运行的命令是存放在一个数组结构中的;使用 exec 执行,推荐方式;(命令在没有终端的环境中使用此方式执行) 第一个参数必须是命令的全路径;
  • CMD command param1 param2 在 /bin/bash 中执行,提供给需要交互的应用;
  • CMD ["param1","param2"] 用于给 ENTRYPOINT 提供参数;
~ ENTRYPOINT

entrypoint是容器的入口;是真正的的用于定义容器启动以后的执行体的;
配置容器启动后执行的命令;

  • 没有指定ENTRYPOINT时,就用CMD指定的命令+参数来启动容器;若指定了ENTRYPOINT,CMD指定的字符串就会变成ENTRYPOINT指令的参数;两者可以组合使用;
  • 若在docker run命令行中指定了-g参数,则-g后面的参数会覆盖cmd参数传递给ENTRYPOINT,若要使run覆盖ENTRYPOINT,需要在run后面加上–entrypoint参数;
  • So可以构建一个镜像,该镜像既可以运行一个默认的命令(CMD指定的),同时也支持通过docker run 命令行为该命令指定可覆盖的选项或标志;
  • ENTRYPOINT不容易被 docker run 提供的参数覆盖,若想被docker run指定的命令覆盖,则需要在docker run命令中指定--entrypoint选项。
  • 每个 Dockerfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效;
  • 两种格式:
  • ENTRYPOINT ["executable", "param1", "param2"] :通过数组的方式为命令指定相应的参数,参数可有可不有;
  • ENTRYPOINT command param1 param2 (shell中执行)
  • 一般使用entrypoint的中括号形式作为docker 容器启动以后的默认执行命令,里面放的是不变的部分,可变部分比如命令参数可以使用cmd的形式提供默认版本,也就是run里面没有任何参数时使用的默认参数。如果我们想用默认参数,就直接run,否则想用其他参数,就run 里面加参数;
  • ENTRYPOINTCMD区别
    ENTRYPOINT 指定了该镜像启动时的入口,CMD 则指定了容器启动时的命令,当两者共用时,完整的启动命令像是 ENTRYPOINT + CMD 这样;使用 ENTRYPOINT 的好处是在我们启动镜像就像是启动了一个可执行程序,在 CMD 上仅需要指定参数;另外在我们需要自定义 CMD 时不容易出错;

>> 3、WORKDIR、ENV、USER

这三个指令用来指定镜像在构建及运行时的环境设置;

~ WORKDIR

使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),将工作目录换到指定路径,以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录;相当于 cd;但是不要在Dockerfile中 使用 RUN cd /usr/..../进行目录的切换;

RUN cd /app
RUN echo "hello" > world.txt

如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello;原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器,这就是对 Dockerfile 构建分层存储的概念不了解所导致的错误;

每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更;第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更;而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化;

因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令;

  • 我们可以使用该指令为Dockerfile中后续的 RUN 、 CMD 、 ENTRYPOINT 等一系类指令设置工作目录,也可以为最终容器设置工作目录;Eg:
  • 可以通过-w标志在运行时覆盖工作目录:docker run -it -w /var/log ubuntu pwd:该命令会将容器内部的工作目录设置为/var/log;
  • 格式为 WORKDIR /path/to/workdir
    可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径;例如:
    WORKDIR /aWORKDIR bWORKDIR cRUN pwd 则最终路径为 /a/b/c
~ ENV
  • 指令用来在镜像构建过程中设置环境变量,为容器里面的环境设置变量;
  • 格式为 ENV <key> <value> ; Eg: ENV PATH /usr/local/nginx/sbin:$PATH
  • 指定一个环境变量,这个新的环境变量可以在后续的任何 RUN 指令中使用,并在容器运行时保持,这就如同在命令前面指定了环境变量前缀一样;
  • 也可以在其他指令中直接使用这些环境变量:ENV TARGET_DIR /opt/appWORKDIR $TARGET_DIR
  • 如果需要,可以通过在环境变量前加上一个反斜杠来进行转义;
  • ENV设置的环境变量会被持久保存到从镜像创建的任何容器中;
  • 也可以使用docker run命令行的-e标志来传递环境变量,但是这个变量只会在运行时有效;
~ USER
  • 指令用来指定该镜像会以什么样的用户去执行;
    Eg:USER nginx:基于该镜像启动的容器会以nginx用户的身份来运行;
  • 格式为 USER daemon;例如: RUN useradd -s /sbin/nologin -M www
  • USER:指定执行该命令的用户;可以指定运行容器时的用户名或 UID,以及组或GID,甚至是两者的结合,后续的 RUN 也会使用指定用户。
  • 当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户;
  • 可以在docker run命令中通过-u选项来覆盖该指令的值;
  • 若不通过USER指令指定用户,默认用户是root

>> 4、VOLUMN、COPY、ADD

这三个指令用来设置镜像的目录和文件;

~ VOLUMN
  • 指令用来向基于镜像创建的容器添加数据卷;mount point挂载目录;创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等;
  • 格式为VOLUME ["/opt/project"]:这条指令将会为基于此镜像创建的任何容器 创建一个名为/opt/project的挂载点;
  • 也可以通过制定数组的方式指定多个卷:VOLUMN ["/opt/project","/data"]
~ COPY

ADD <源路径> <目标路径>

COPY 指令将从构建上下文目录中 <源路径> 指定的文件/目录,复制到新的一层的镜像内的 <目标路径> 位置;若源路径是一个目录,那么整个目录都将被复制到容器中,包括文件系统元数据;若源文件为任何类型的文件,则该文件会随同元数据一起被复制;(以 / 结尾的路径,Docker会将它作为目录进行复制;)

指令非常类似于ADD,它们根本的不同是COPY只关心在构建上下文中复制本地文件,而不会去做文件提取和解压的工作;

  • 源路径:相对于构建上下文的相对路径;
    必须是一个与当前构建环境相对的文件或目录,本地文件都放到和Dockerfile同一个目录下;不能复制该目录之外的任何文件,因为构建环境会上传到Docker守护进程,而复制是在Docker守护进程中进行的;任何位于构建环境之外的东西都是不可用的!
    源路径可以是多个,也可以是通配符;
  • 目标路径:镜像中文件夹的位置;
    COPY的目标位置 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定);
    目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录;
  • 任何由该指令创建的文件或者目录的UID和GID都会设置为0;
  • 使用 COPY 指令,源文件的各种元数据都会保留,比如读、写、执行权限、文件变更时间等;这个特性对于镜像定制很有用,特别是构建相关文件都在使用 Git 进行管理的时候;
~ ADD

ADD <源文件位置> <目的文件位置>

指令用来将构建环境下的 文件/目录 复制到镜像中;也就是 往容器里面添加文件;相当于COPY,但是比COPY功能更强大;

源路径

  • 可以是Dockerfile所在目录的一个相对路径;
  • 可以是一个URL:可以将远程文件加入到容器里面;
    Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去,下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整;
    另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩;所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理;
  • 可以是一个tar文件:压缩格式为 gzip, bzip2 以及 xz 的情况下,复制进容器会自动解压这个压缩文件到<目标路径>
    在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu 中:ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz / 但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令了;

目标路径
若目的位置不存在,Docker会创建这个全路径,包括路径中的任何目录;新创建的目录和文件的模式为0755,并且UID和GID都是0;

  • ADD指令会使得构建缓存失效,从而可能会令镜像构建变得比较缓慢:若通过ADD指令向镜像添加一个文件或者目录,那么将是Dockerfile中的后续指令都不能继续使用之前的构建缓存;

>> 5、ONBUILD

为镜像添加触发器trigger;

  • ONBUILD:为镜像添加触发器trigger,当一个镜像被用作其他镜像的基础镜像时,这个触发器将会被执行;配置 当所创建的镜像 作为其它新创建镜像的基础镜像时,所执行的操作指令;
  • 格式为 ONBUILD [INSTRUCTION]
  • 在构建本镜像时不生效,在基于此镜像构建镜像时生效;当子镜像在构建时,会插入触发器中的指令;ONBUILD触发器会按照在父镜像中指定的顺序执行,并且只被执行一次,也就是说只能在子镜像中执行,而不会在孙子镜像中执行;
  • 触发器会在构建过程中插入新指令,我们可以认为这些指令是紧跟在FROM之后指定的;
  • 触发器可以是任何构建指令:ONBUILD ADD . /app/srcONBUILD RUN cd /app/src && make,代码将会在创建的镜像中加入ONBUILD触发器,ONBUILD指令可以在镜像上运行docker inspect命令来查看;
  • Dockerfile如何去掉env变量 dockerfile env arg_docker_02

  • 可以将含有ONBUILD指令的Dockerfile文件作为一个通用的Web应用程序的模板,可以基于这个模板来构建Web应用程序:
    首先创建一个含有ONBUILD指令的Dockerfile文件,并以此来构建全新镜像A,然后再构建一个名为B的镜像FROM A(以镜像A为基础镜像),构建镜像B时,先执行DockerfileB的FROM指令,之后执行基础镜像A的Dockerfile文件中的ONBUILD指令,然后才会继续执行DockerfileB中的第二条指令及后续各指令;
  • ONBUILD指令中不能使用的指令:FROM、MAINTAINER、ONBUILD;(防止在Dockerfile构建过程中产生递归调用的问题)

部分参考自:https://cloud.tencent.com/developer/article/1004349

>> Dockerfile 构建过程

1、Docker通过Dockerfile构建镜像过程:

  • 首先,docker会从基础镜像运行一个容器,也就是在dockerfile文件中使用FROM命令指定的镜像名;
  • 然后会执行一条指令,这条指令会对容器进行修改;
  • 对修改后的容器执行类docker commit的操作,提交一个新的镜像层;
  • 根据新的镜像层,运行一个新的容器;
  • 再执行dockerfile的下一条指令,反复如此,直至所有指令执行完毕;

2、使用dockerfile构建镜像的优势:

  • 通过使用中间层镜像进行调试的一大好处:调试错误;
  • 构建缓存:使得镜像的构建过程非常高效;
    使用docker build -t .命令构建镜像,通过docker build命令的返回结果,看到dockerfile每一步的执行的情况;期间可能会生成中间层镜像id,同时在得到一个中间层镜像id之后,还会有一条信息:会删除中间层建立的容器;在build返回结果的结尾处会返回 最新生成的镜像id;docker build命令会删除中间层创建的容器,但是不会删除中间层创建的镜像,so可以使用docker run命令通过一个中间层镜像运行一个容器;从而查看每一步构建后镜像的实际状态,可以通过这点调试容器的构建
  • 每一步的构建过程,都会将结果提交为镜像,so docker镜像的构建过程会将之前的镜像看做是缓存,使得镜像的构建过程非常高效;但是有些情况下不想使用构建缓存:比如构建命令中包含update命令时,希望每次docker都能刷新缓存,这样就能得到最新的版本;

不使用缓存:

  • (1)docker build --no-cache
  • (2)在dockerfile里面使用ENV配置环境变量:ENV REFRESH_DATE 2018-05-20,这个环境变量用来表明该镜像模板最后的更新时间;使用REFRESH_DATE 环境变量来标识缓存刷新的时间,修改时间就会带来后面命令的换存刷新;也就是说,如果想刷新一个构建,只需修改ENV指令中的日期;这使Docker在命中ENV指令时开始重置这个缓存,并运行后续指令而无须依赖该缓存;