什么是 Dockerfile?
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。使用docker build
命令,用户可以创建基于基础镜像的自定义镜像。
Dockerfile格式
# Comment INSTRUCTION arguments # INSTRUCTION表示指令(不止一条) 复制代码
Dockerfile指令详解
FROM-指定基础镜像
指定基础镜像,并且Dockerfile中第一条指令必须是FROM指令,且在同一个Dockerfile中创建多个镜像时,可以使用多个FROM指令。
语法格式如下:
FROM <image> FROM <image>:<tag> 复制代码
其中<tag>
是可选项,如果没有选择,那么默认值为latest
。
如果不以任何镜像为基础,那么写法为:FROM scratch
。
RUN-运行指定的命令
运行指定的命令。
包含两种语法格式,如下所示:
# shell格式:就像在命令行中输入的Shell脚本命令一样。 RUN <command> # exec格式:就像是函数调用的格式。 RUN ["executable", "param1", "param2"] 复制代码
第一种后边直接跟shell命令。
- 在linux操作系统上默认为
/bin/sh -c
- 在windows操作系统上默认为
cmd /S /C
第二种是类似于函数调用。可将executable理解成为可执行文件,后面就是两个参数。
样例:
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME' RUN ["/bin/bash", "-c", "echo hello"] 复制代码
注意:
多行命令不要写多个RUN,原因是Dockerfile中每一个指令都会建立一层。多少个RUN就构建了多少层镜像,会造成镜像的臃肿、多层,不仅仅增加了构件部署的时间,还容易出错。RUN书写时的换行符是
\
CMD-容器启动时要运行的命令
容器启动时要运行的命令。
包含三种语法格式,如下所示:
# 第一种就是shell这种执行方式和写法 CMD command param1 param2 # 第二种是可执行文件加上参数的形式(推荐) CMD ["executable","param1","param2"] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数 CMD ["<param1>","<param2>",...] 复制代码
推荐使用第二种格式,执行过程比较明确。第一种格式实际上在运行的过程中也会自动转换成第二种格式运行,并且默认可执行文件是sh
。
样例:
CMD [ "sh", "-c", "echo $HOME" ] CMD [ "echo", "$HOME" ] 复制代码
注意:
这里边包括参数的一定要用双引号,不能是单引号。千万不能写成单引号。原因是参数传递后,docker解析的是一个JSON array.
ENTRYPOINT-为容器指定默认运行程序
类似CMD指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序。
在写Dockerfile时, ENTRYPOINT或者CMD指令会自动覆盖之前的ENTRYPOINT或者CMD指令。即ENTRYPOINT或者CMD指令只能写一条,如果写了多条,那么,ENTRYPOINT或者CMD都只有最后一条生效。
在Docker镜像运行时, 用户也可以在命令行指定具体命令, 覆盖在Dockerfile里的命令。
与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run
命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给ENTRYPOINT指令指定的程序,不过,docker run
命令的--entrypoint
选项的参数可覆盖ENTRYPOINT指令指定的程序。
其语法如下:
# exec格式(推荐) ENTRYPOINT ["executable", "param1", "param2"] # shell格式 ENTRYPOINT command param1 param2 复制代码
第一种就是可执行文件加参数。与CMD
指令一样,ENTRYPOINT
也更加推荐使用exec
格式。
第二种就是shell格式。
样例:
FROM ubuntu RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* ENTRYPOINT ["curl", "-s", "http://ip.cn"] 复制代码
# 将其构建成镜像ubuntu:v1.2,下面我们创建并启动容器: docker run -it ubuntu:v1.2 # 将会在控制台输出我们相应的公网IP信息! # 此时,如果我们还需要获取HTTP头信息时,我们可以这样: docker run -it ubuntu:v1.2 -i 复制代码
CMD与ENTRYPOINT的异同点
这俩命令非常像,而且还可以配合使用。
相同点:
- 只能写一条,如果写了多条,那么只有最后一条生效。
- 容器启动时才运行,运行时机相同。
不同点:
- ENTRYPOINT不会被运行的command覆盖,而CMD则会被覆盖。
- 如果我们在Dockerfile中同时写了ENTRYPOINT和CMD,并且CMD指令不是一个完整的可执行命令,那么CMD指定的内容将会作为ENTRYPOINT的参数。如下所示:
FROM ubuntu ENTRYPOINT ["rm", "docker2"] CMD ["-rf"] 复制代码
它真正执行的命令将会是:rm docker2 -rf
。
- 如果我们在Dockerfile种同时写了ENTRYPOINT和CMD,并且CMD是一个完整的指令,那么它们两个会互相覆盖,谁在最后谁生效。如下所示:
FROM ubuntu ENTRYPOINT ["top", "-b"] CMD ls -al 复制代码
那么将执行ls -al
,top -b
不会执行。
下表显示了针对不同的ENTRYPOINT/CMD进行组合后执行的命令:
No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”, “p1_entry”] | |
No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | exec_entry p1_entry exec_cmd p1_cmd | |
CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | exec_entry p1_entry p1_cmd p2_cmd | |
CMD exec_cmd p1_cmd | /bin/sh -c exec_cmd p1_cmd |
虽然这2个指令功能类似,但是这两个指令不是互斥的. 在很多情况下, 你可以组合ENTRYPOINT和CMD指令, 提升最终用户的体验。同时我们从上表也发现在使用SHELL去执行ENTRYPOINT时,ENTRYPOINT会无视从CMD传来的任何参数,所以CMD只传参数是无效的。
组合ENTRYPOINT和CMD指令的最佳实践:
组合使用ENTRYPOINT和CMD命令式, 确保你一定用的是exec表示法. 如果有其中一个用的是shell表示法, 或者一个是shell表示法, 另一个是exec表示法, 你永远得不到你预期的效果。只有当ENTRYPOINT和CMD都用exec表示法, 才能得到预期的效果。
COPY-复制文件或者目录到容器里指定路径
从上下文目录中复制文件或者目录到容器里指定路径。
语法格式如下:
COPY [--chown=<user>:<group>] <src>... <dest> COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] 复制代码
参数说明:
- [--chown=:] :可选参数,用户改变复制到容器内文件的拥有者和属组。
- <源路径> :源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则。例如:
COPY hom* /mydir/
,COPY hom?.txt /mydir/
。源路径必须是build上下文中的路径,不能是其父目录中的文件。如果源路径是目录,则其内部文件或子目录会被递归复制,但源路径目录自身不会被复制。如果指定了多个源路径,或在源路径中使用了通配符,则目标路径必须是一个目录,且必须以/
结尾。 - <目标路径> :容器内的指定路径,建议为目标路径使用绝对路径,否则,COPY指令则以WORKDIR为其起始路径;如果目标路径事先不存在,它将会被自动创建,这包括其父目录路径。
样例:
# 复制宿主机文件index.html到容器/data/html/index.html COPY index.html /data/html/index.html # 复制宿主机data目录下文件(包括子目录)到容器/data/目录下,并不会复制目录本身 COPY data /data/ 复制代码
ADD-复制文件或者目录到容器里指定路径
ADD 指令和 COPY 的使用类似 (同样需求下,官方推荐使用 COPY) ,功能也类似。除此之外,ADD还支持使用TAR文件和URL路径,并且会将tar压缩文件(gzip, bzip2以及 xz格式)解压缩,如果指定的是url,会从指定的url下载文件放到目录中( 如果url下载的文件为tar文件,则不会展开)。
其语法格式如下:
ADD [--chown=<user>:<group>] <src>... <dest> ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] 复制代码
样例:
ADD /data/src/nginx-1.14.0.tar.gz /data/src/ 复制代码
# 构建镜像 docker build -t nginx:v1.1 . # 创建容器 docker run --rm --name nginx -it nginx:v1.1 ls /data/src # 我们可以发现已经解压了nginx-1.14.0.tar.gz文件 复制代码
LABEL-为镜像指定标签
为镜像指定标签,其格式为:
LABEL <key1>=<value1> <key2>=<value2> ... 复制代码
LABEL
后面是键值对,多个键值对以空格进行隔开,如果value中包含空格,请使用""
将value进行圈起来。
# 如果太长需要换行的话,则使用\符号 LABEL name=test \ description="a container is used to test" 复制代码
我们可以使用docker inspect
命令,来查看镜像的标签:
docker inspect --format '{{json .Config.Labels}}' test | python3 -m json.tool 复制代码
其中,“test”为容器名称,”python3 -m json.tool“为将其格式化为JSON输出。
MAINTAINER-指定生成镜像的作者名称
用于指定生成镜像的作者名称,其格式为:
MAINTAINER <name> 复制代码
MAINTAINER
指令已经被弃用,可以使用LABEL
指令进行替代,样例如下:
LABEL maintainer='Stephen Chow' 复制代码
说明:
LABEL会继承基础镜像中的LABEL,如遇到key相同,则值覆盖。
EXPOSE-为容器打开指定要监听的端口以实现与外部通信
用于为容器打开指定要监听的端口以实现与外部通信,这个只是声明,真正要暴露这个端口需要再构建容器的时候使用"-P"选项。其格式为:
EXPOSE <port> [<port>/<protocol>...] 复制代码
示例:
EXPOSE 80/tcp EXPOSE 80/udp 复制代码
如果想使得容器与主机的端口有映射关系,需要在启动容器时指定-P
.
# 这里的“-P”选项是关键,在启动容器的使用使用-P, # Docker会自动分配一个端口和转发指定的端口, # 使用-p可以具体指定使用哪个本地的端口来映射对外开放的端口。 docker run --rm --name nginx -itd -P nginx:v1.4 复制代码
ENV-设置环境变量
设置环境变量,无论是接下来的指令(如ENV
、ADD
、COPY
等,其调用格式为$variable_name
或${variable_name}
),还是在容器中运行的程序,都可以使用这里定义的环境变量。
它有两种语法格式,如下所示:
ENV <key> <value> ENV <key1>=<value1> <key2>=<value2> ... 复制代码
两者的区别就是第一种是一次设置一个,第二种是一次设置多个。
示例:
ENV word hello ENV guodong xxxx ENV name1=ping name2=on_ip RUN echo $word 复制代码
注意:
如果你想通过CMD或者ENTRYPOINT指令的exec格式来打印环境,就像下面这样:
CMD ["echo", $MODE]
CMD ["echo", "$MODE"]
这样都是不能正确输出环境变量的值的,你可以改成
exec格式
来执行shell命令
,如下所示:
CMD ["sh", "-c", "echo $MODE"]
如此,就能正确输出环境变量的值了!