查看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>...]
;
>> 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 里面加参数;
- ENTRYPOINT 和 CMD 的区别:
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 /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为/a/b/c
;
~ ENV
- 指令用来在镜像构建过程中设置环境变量,为容器里面的环境设置变量;
- 格式为
ENV <key> <value>
; Eg:ENV PATH /usr/local/nginx/sbin:$PATH
- 指定一个环境变量,这个新的环境变量可以在后续的任何 RUN 指令中使用,并在容器运行时保持,这就如同在命令前面指定了环境变量前缀一样;
- 也可以在其他指令中直接使用这些环境变量:
ENV TARGET_DIR /opt/app
、WORKDIR $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/src
、ONBUILD RUN cd /app/src && make
,代码将会在创建的镜像中加入ONBUILD触发器,ONBUILD指令可以在镜像上运行docker inspect
命令来查看; - 可以将含有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指令时开始重置这个缓存,并运行后续指令而无须依赖该缓存;