在Docker中,手动制作镜像相对来说比较还是比较麻烦的,尤其是在需要对现有镜像进行修改时,制作完成后在传输上也很占用带宽。
利用Dockerfile可以快速自动地构建需要的镜像。
Dockerfile 是Docker用于构建镜像的文本文件,它描述了组装镜像的步骤。
FROM
1 | FROM <image>:<tag> |
FROM 指令是Dockerfile的第一条构建指令,其功能是为后面的指令提供基础镜像,基础镜像可以选择任何有效的镜像。Dockerfile 中可以有多个FROM 指令,对应的也会构建多个镜像(多阶段构建的应用)。参数tag 如果为空,则默认是latest,如果指定的tag镜像不存在,则直接返回错误。需要注意的是:
当我们要构建一个操作系统基础镜像的时候,使用FROM scratch表明我们要构建镜像中的第一个文件层,scratch是Docker保留镜像,镜像仓库中的任何镜像都不能使用这个名字。
ENV
1 | ENV <key> <value> |
ENV 指令的作用是为镜像声明环境变量。ENV 所声明的环境变量可以供后面的指令使用(如COPY, ADD, WORKDIR等),使用格式为${var}, $var。
WORKDIR
1 | WORKDIR <path> |
WORKDIR 是为镜像设置当前工作目录。
需要注意的是:Dockerfile 与linux shell 有很多相同之处,但是如果单纯将Dockerfile 当成linux shell 来写,可能会犯很多错误,举个例子:
1 | RUN cd /app |
上面的Dockerfile 命令将会报错找不到helloworld.txt,或者/app/helloworld.txt 里面内容根本不是”hello world“。前面我们讲到,每运行一个指令,会生成一个新的中间镜像,上面的例子中,当第一个RUN指令运行cd命令后,仅仅是属于当前进程中的一个内存状态变化。而执行第二个RUN指令时,根据Dockerfile 的分层存储机制,启动的是一个全新的容器进程,自然不可能继承第一层的内存状态变化。所以如果需要改变各层之间的工作目录,则需要使用WORKDIR
COPY
1 | COPY <src> <dest> |
COPY 指令用于将主机的资源文件在生成镜像的时候拷贝到镜像中。src 可以是指定多个源,但其路径必须在上下文中。
需要注意的是:
src可以使用通配符,例如COPY ./home/con* /mydir/
,这样以con
开头的文件拷贝到都被拷贝到目标目录中。
如果源路径是一个目录,那么目录下的内容都会被拷贝到镜像中,但是目录本身不会被拷贝(这点与linux 的cp命令有区别);
dest 必须是目标镜像中的绝对路径和或者基于WORKDIR 指令的相对路径,同样目标路径也可以使用通配符。
若目标路径是一个目录,则必须以符号 / 结束,如果目标镜像路径不存在,则在构建时创建完整的路径。
ADD
1 | ADD <src> <dest> |
ADD 命令与COPY命令很相似。ADD的src可以是一个指向网络文件的URL,也可以是指向本地的压缩归档文件。
RUN
1 | RUN <command> # shell格式 |
RUN命令在容器中有两种运行方式。
当使用shell 格式的时候,则通过/bin/sh -c 的方式运行。
当使用的是exec格式的时候,命令是直接以JSON的方式传递给Docker 解析执行。因为exec不会在shell中执行,故环境变量的参数不会被替换。如果想以exec的方式调用shell程序,可以写成CMD["sh","-c","param1", "param2"
。
如果想利用RUN命令一次运行多条执行,可以利用符号 && 进行连接。如下面的例子:
1 | RUN sh -c "yum -y install gcc \ |
CMD
1 | CMD <command> # shell格式 |
CMD的主要作用看起来也是主要执行一些命令,但CMD指令与RUN指令不同,RUN指令主要是在构建镜像的时候执行命令,CMD在构建的时候并不执行任何命令,而是在启动容器时将CMD指令作为第一条执行的命令。
需要注意的是:
Dockerfile中虽然可以有多条CMD命令,但是只有最后一条CMD命令会生效。
如果用户在命令行运行docker run
命令时同时指定了命令参数,则会CMD指令中的命令会被覆盖。
ENTRYPOINT
1 | ENTRYPOINT <command> # shell格式 |
ENTRYPOINT 与 CMD指令的用途相似,都是在镜像启动时执行命令,但它们又有些不同:
docker run
以及 CMD 所传递的参数,且不能接受Unix信号,即如果执行 docker stop <container>
命令时,ENTRYPOINT进程无法无法接收SIGTERM 信号。docker run
传入的命令参数可以覆盖CMD的指令内容,并附加到ENTRYPOINT指令的参数中。真实的情况比上面的两点复杂,这是docker官方给出的参考:
针对CMD和ENTRYPOINT有以下的使用建议:如果Docker镜像的用途是作为应用程序或者服务,例如Mysql 则使用EXEC格式的ENTRYPOINT指令,启动命令相对固定,只需要提供额外的参数进行替换便可启动容器。而如果想为容器设置启动命令,则使用CMD指令,通过docker run 指定命令参数进行替换。
除此之外,如果我们使用了ENTRYPOINT,但是我们又想设置启动命令,则可以使用 –entrypoint 参数,可以同时覆盖CMD以及ENTRYPOINT指令.
EXPOSE
1 | EXPOSE <port1> <port2> … |
EXPOSE指令的作用是声明容器提供服务的端口,这仅仅是一个声明,并不会在运行容器时因为这个声明而开启所声明的端口。使用EXPOSE有两个好处:方便容器的使用者理解容器服务的守护端口,方便做端口映射,另一个则是使用 docker run -P
的时候,能够随机映射EXPOSE声明的端口。
Dockerfile 的指令都是单独执行的。除了FROM 指令以外,每一条指令的执行都是在其上一条指令所生成镜像的基础上执行,每一条指令都会基于原来的镜像生成一个新的镜像层。Dockerfile所生成的最终镜像就是在基础镜像叠加一层一层的镜像所组建的。
使用体积小的Linux镜像,比如使用alpine作为基础镜像(仅有5MB大小),如果alpine无法兼容一些软件时,可以考虑使用Ubuntu18.04镜像,因为其63MB;
将多条RUN指令合并成以条指令,这样做的好处就是仅生成一个临时镜像;
修改dockerfile的时候,尽可能把修改的内容放在最后,这样可以充分利用缓存镜像;
1 | # 关于缓存镜像 |
可以使用”.dockerignore”忽略构建docker镜像时不需要的文件,从而减小镜像体积;