docker6:Dockerfile详解
简单介绍
dockerfile 是一种可以被 docker 程序解释的脚本,由一条条指令组成。通过编写 dockerfile 可以做到:
- 定制化 docker 镜像。
- 整合下载镜像、启动容器、编辑可写层等等操作,可重复构建,提升效率。
- 随时维护、修改、分享 dockerfile。
dockerfile编写格式
-
dockerfile 整体由两类语句组成:
- 注释信息。
- 指令和参数(一行一个指令)。
-
dockerfile 指令名不区分大小写,但为了方便和参数区分,通常使用大写字母。
-
dockerfile 中指令按从上到下顺序依次执行。
-
dockerfile 中第一个非注释行必须是 FROM 指令,其用来指定制作当前镜像依据哪个基础镜像。
-
dockerfile 中需要调用的文件必须跟 dockerfile 文件在同一目录或者其子目录下,其它路径无效。
dockerfile指令介绍
FROM
-
介绍
- FROM 指令必须为 dockerfile 文件的第一个非注释行,用于指定构建镜像所使用的基础镜像,后续的指令运行都要依靠此基础镜像所提供的的环境(简单说就是假如 dockerfile 中所引用的基础镜像里面没有 mkdir 命令,那后续的指令是没法使用 mkdir 命令的。)
- 实际使用中,如果没有指定仓库,docker build 会先从本机查找是否有此基础镜像,如果没有会默认去 Docker Hub Registry 拉取,再找不到就会报错。
-
语法格式
FROM [--platform=<platform>] <image> [AS <name>] FROM [--platform=<platform>] <image>[:<tag>] [AS <name>] FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
-
语法说明
- Digest:镜像的哈希码,防止镜像被冒名顶替。
MAINTAINER(deprecated)
-
介绍
- 用于让 dockerfile 的作者提供个人的信息。
- dockerfile 并不限制 MAINTAINER 指令的位置,但是建议放在 FROM 指令之后。
- 在较新的 docker 版本中,已经被 LABEL 替代。
-
语法格式
MAINTAINER <name>
LABEL
-
介绍
- 同 docker run -l。
- 让用户为镜像指定各种元数据(键值对的格式)。
-
语法格式
LABEL <key>=<value> <key>=<value> <key>=<value> ...
COPY
-
介绍
- 复制宿主机上的文件到目标镜像中。
-
语法格式
COPY [--chown=<user>:<group>] <src>... <dest> COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
-
语法说明
- <src>:要复制的源文件或者目录,支持通配符。
- <src> 必须是 build 上下文中的目录,不能是其父目录中的文件。
- 如果 <src> 是目录,则其内部的文件或则子目录会被递归复制,但 <src> 目录本身不会被复制。
- <dest>:目标路径,即正创建的镜像的文件系统路径,建议使用绝对路径,否则,COPY 指令会以 WORKDIR 为其起始路径。
- 如果 <dest> 不存在,它将会被自动创建,包括其父目录路径。
- 如果指定了多个 <src>,或者 <src> 中使用通配符,则 <dest> 必须是一个目录,且必须以 / 结尾。
- 如果路径中如果包含空白字符,建议使用第二种格式用引号引起来,否则会被当成两个文件。
ADD
-
介绍
-
ADD 指令跟 COPY 类似,不过它还支持使用 tar 文件和 URL 路径。
- 当拷贝的源文件是 tar 文件时,会自动展开为一个目录并拷贝进新的镜像中,而通过 URL 获取到的 tar 文件不会自动展开。
- 主机可以联网的情况下,docker build 可以将网络上的某文件引用下载并打包到新的镜像中。
-
-
语法格式
ADD [--chown=<user>:<group>] <src>... <dest> ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
WORKDIR
-
介绍
- 同 docker run -w。
- 指定工作目录,可以指多个,每个 WORKDIR 只影响他下面的指令,直到遇见下一个 WORKDIR 为止。
- WORKDIR 也可以调用由 ENV 指令定义的变量。
-
语法格式
WORKDIR path # path为相对路径或者绝对路径。
-
语法说明
- 相对路径是相对于上一个 WORKDIR 指令的路径,如果上面没有 WORKDIR 指令,那就是当前 dockerfile 文件的目录。
VOLUME
-
介绍
- docker run -v 简化版。
- 用于在镜像中创建一个挂载点目录。上一章中有提到 Volume 的几种挂载方式,忘记的可以回顾下。在 dockerfile 中只支持 docker 管理的卷,也就是说只能指定容器内的路径,不能指定宿主机的路径。
-
语法格式
VOLUME ["<mountpoint>"]
EXPOSE
-
介绍
- 同 docker run --expose。
- 指定容器中待暴露的端口。比如容器提供的是一个 https 服务且需要对外提供访问,那就需要指定待暴露 443 端口,然后在使用此镜像启动容器时搭配 -P 的参数才能将待暴露的状态转换为真正暴露的状态,转换的同时 443 也会转换成一个随机端口,跟 -p :443 一个意思。
- EXPOSE 指令可以一次指定多个端口,例如:EXPOSE 11111/udp 11112/tcp
-
语法格式
EXPOSE <port> [<port>/<protocol>...]
-
语法说明
- <protocol> 用于指定协议类型,如果不指定,默认 TCP 协议。
ENV
-
介绍
- 同 docker run -e。
- 为镜像定义所需的环境变量,并可被 ENV 指令后面的其它指令所调用。调用格式为 $variable_name 或者 ${variable_name}。
- docker run -e 的优先级高于 dockerfile 中的 ENV,但是不会影响到 dockerfile 中已经引用过此变量的文件名,下面有举例说明。
-
语法格式
ENV <key> <value> ENV <key>=<value> ...
-
语法说明
- 第一种格式一次只能定义一个变量,<key> 之后所有内容都会被视为 <value> 的组成部分。
- 第二种格式一次可以定义多个变量,每个变量为一个 "
= " 的键值对,如果 <value> 中包含空格,可以用反斜线 \ 进行转义,也可以为 <value> 加引号,另外参数过长时可用反斜线做续行。 - 定义多个变量时,建议使用第二种方式,因为 dockerfile 中每一行都是一个镜像层,构建起来比较吃资源。
-
举例
# 删除历史容器(注意在测试主机操作,别误删掉重要容器)。 [0 root@docker1,172.16.15.21:~]# docker stop `docker ps -aq` [0 root@docker1,172.16.15.21:~]# docker rm `docker ps -aq` # 编写dockerfile将testfile拷贝到容器内。 [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test1 [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test1 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# echo 'test dockerfile1' >testfile [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# cat Dockerfile # test dockerfile1 FROM busybox:latest ENV file=aaa ADD ./testfile /usr/local/${file}/ # 构建 dockerfile。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker build -t busybox:v1 . # -t:指定镜像名和标签。 # .:代表当前 PATH。 # 根据此镜像启动容器,查看file变量的值。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 --rm busybox:v1 ls /usr/local/aaa/ testfile [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 --rm busybox:v1 printenv | grep file file=aaa # 接下来使用docker run -e来指定file变量,再查看file变量的值。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 -e file=bbb --rm busybox:v1 ls /usr/local/aaa/ testfile [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 -e file=bbb --rm busybox:v1 ls /usr/local/bbb/ ls: /usr/local/bbb/: No such file or directory [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 -e file=bbb --rm busybox:v1 printenv | grep file file=bbb # 由此例证明了上面指令介绍中的第三句话。这是因为docker build属于第一阶段,docker run属于第二阶段。第一阶段定义file变量的值aaa已经被引用了,生米已煮成熟饭,后续阶段再改file变量的值也影响不了aaa。
RUN
-
介绍
- 用于指定 docker build 过程中运行的程序,可以是任何命令。
- RUN 指令后所执行的命令必须在 FROM 指令后的基础镜像中存在才行。
-
语法格式
RUN <command> RUN ["executable", "param1", "param2"]
-
语法说明
- <command> 通常是一个 shell 命令,系统默认会把后面的命令作为 shell 的子进程来运行,以 /bin/sh -c 来运行它,比如:RUN /bin/bash -c "echo $HOME" 。
- 第二种格式会被解析成 JSON 数组,这代表必须括号内的引用必须使用双引号。此种方式调用 shell,需使用 RUN [ "sh", "-c", "echo $HOME" ],再比如RUN [ "python3", "test.py" ]
CMD
-
介绍
-
指定启动容器的默认要运行的程序,也就是 PID 为 1 的进程命令,其运行结束后容器也会终止。如果不指定,默认是 bash。
-
CMD 指令指定的默认程序会被 docker run 命令行指定的参数所覆盖。
-
dockerfile 中可以存在多个 CMD 指令,但仅最后一个生效。因为一个 docker 容器只能运行一个 PID 为 1 的进程。
-
类似于 RUN 指令,也可以运行任意命令或程序,但是两者的运行时间点不同。
- RUN 指令运行在 docker build 的过程中,而 CMD 指令运行在基于新镜像启动容器时(docker run)。
-
-
语法格式
CMD command param1 param2 CMD ["executable","param1","param2"] CMD ["param1","param2"]
-
语法说明
- 前两种语法格式同 RUN 指令。
- 第三种则需要结合 ENTRYPOINT 指令使用,CMD 指令后面的命令作为 ENTRYPOINT 指令的默认参数。如果 docker run 命令行结尾有参数指定,那 CMD 后面的参数不生效。
ENTRYPOINT
-
介绍
- 类似 CMD 指令的功能,用于为容器指定默认运行程序。
- dockerfile 中可以存在多个 ENTRYPOINT 指令,但仅最后一个生效。
- 与 CMD 区别在于,由 ENTRYPOINT 启动的程序不会被 docker run 命令行指定的参数所覆盖,而且这些命令行参数会被当做参数传递给 ENTRYPOINT 指令指定的程序。
- 不过,docker run 的 --entrypoint 选项的参数可以覆盖 ENTRYPOINT 指定的默认程序,下面举例说明。
-
语法格式
ENTRYPOINT command param1 param2 ENTRYPOINT ["executable", "param1", "param2"]
-
举例
-
CMD 指令:测试命令行参数可以代理 dockerfile 中 CMD 指定的程序。
# 编写dockerfile [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test2 [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test2 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test2]# cat Dockerfile # test dockerfile2 FROM busybox:latest LABEL maintainer="merle <merle@freeit.com>" app="httpd" ENV WEBDIR="/server/sites/test/" RUN mkdir -p ${WEBDIR} && echo 'This is a test web' >${WEBDIR}/index.html CMD [ "sh", "-c", "/bin/httpd -f -h ${WEBDIR}" ] # 构建 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test2]# docker build -t httpd:v1 . # 启动容器,并在结尾传入命令。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test2]# docker run -it --rm --name web01 httpd:v1 ls /server/sites/test/ index.html # 可以看出命令行的参数已经替代了原本的CMD指令指定的httpd。
-
ENTRYPOINT 指令。
# 编写dockerfile [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test3 [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test3 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test3]# cat Dockerfile # test dockerfile3 FROM busybox:latest LABEL maintainer="merle <merle@freeit.com>" app="httpd" ENV WEBDIR="/server/sites/test/" RUN mkdir -p ${WEBDIR} && echo 'This is a test web' >${WEBDIR}/index.html ENTRYPOINT [ "sh", "-c", "/bin/httpd -f -h ${WEBDIR}" ] # 构建 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test3]# docker build -t httpd:v2 . # 启动容器,并在结尾传入命令,可以看到夯在了前台没有反应。这是因为容器把"ls /server/sites/test/"当做参数传给了httpd程序,只是httpd不识别罢了 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test3]# docker run -it --rm --name web01 httpd:v2 ls /server/sites/test/ # 新打开一个xshell窗口,kill掉web01容器,使用--entrypoint参数再测试一下。 [0 root@docker1,172.16.15.21:~]# docker kill web01 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test3]# docker run -it --rm --entrypoint="" --name web01 httpd:v2 ls /server/sites/test/ index.html # 可以看出使用"--entrypoint"参数后,docker run结尾的参数覆盖了ENTRYPOINT指定的默认程序。
-
CMD 指令 + ENTRYPOINT 指令。
# 使用CMD的第三种语法:CMD指令后面的内容作为参数传给ENTRYPOINT指定后面的程序。 [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test4 [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test4 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test4]# cat Dockerfile # test dockerfile4 FROM busybox:latest LABEL maintainer="merle <merle@freeit.com>" app="httpd" ENV WEBDIR="/server/sites/test/" RUN mkdir -p ${WEBDIR} && echo 'This is a test web' >${WEBDIR}/index.html CMD [ "/bin/httpd -f -h ${WEBDIR}" ] ENTRYPOINT [ "sh", "-c" ] # 构建。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test4]# docker build -t httpd:v3 . # 启动容器,并在结尾传入命令,此种方式docker run结尾的参数需要用双引号。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test4]# docker run -it --rm --name web01 httpd:v3 "ls /server/sites/test/" index.html # 可以看到docker run结尾的参数覆盖了CMD后面指定的参数。
-
USER
-
介绍
- 用于指定 docker build 过程中任何 RUN、CMD 等指令的用户名或者 UID。
- 默认情况下容器的运行用户为 root。
-
语法格式
USER <user>[:<group>] USER <UID>[:<GID>]
-
语法说明
- 实践中 UID 需要是 /etc/passwd 中某用户的有效 UID,否则 docker run 命令将运行失败。
HEALTHCHECK
-
介绍
- 顾名思义,健康检查。此指令的就是告诉 docker 如何检查容器是否正常工作。
-
语法格式
HEALTHCHECK [OPTIONS] CMD command HEALTHCHECK NONE
-
语法说明
-
HEALTHCHECK 指令就是定义一个 CMD,在 CMD 后面编写一条命令去判断我们的服务运行是否正常。
-
检查肯定不是一次性的,所以 OPTIONS 就是指定检查的频率。
- --interval=DURATION:每隔多久检查一次,默认30s。
- --timeout=DURATION:超时时长,默认30s。
- --start-period=DURATION:启动健康检查的等待时间,默认 0s。因为容器启动成功时,进程不一定立马就启动成功,那过早开始检查就会返回不健康。
- --retries=N:检查一次失败就返回不健康未免太武断,所以默认值是3,三次重试。
-
CMD 健康检测命令发出时,返回值有三种情况:
- 0:成功。
- 1:不健康。
- 2:保留,无实际意义。
-
HEALTHCHECK NONE 就是不做健康检查。
-
SHELL
-
介绍
- 用来指定运行程序默认要使用的 shell 类型,因为 windows 环境默认是 powershell。
- 此指令一般不会使用。
-
语法格式
SHELL ["executable", "parameters"]
STOPSIGNAL
-
介绍
- 指定发送使容器退出的系统调用信号。docker stop 之所以能停止容器,就是发送了15的信号给容器内 PID 为 1 的进程。
- 此指令一般不会使用。
-
语法格式
STOPSIGNAL signal
ARG
-
介绍
- ARG 命令跟 ENV 类似,也是指定一个变量,但 ENV 指令配合 -e 参数是在 docker run 过程中传参,而使用 ARG 指令配合 --build-arg 参数是在 docker build 过程中传参,下面举例说明。
-
语法格式
ARG <name>[=<default value>]
-
举例
# 编写dockerfile [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test5 [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test5 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# echo 'This is a test web' >index.html # 测试脚本内容 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# cat entrypoint.sh #!/bin/sh cat > /etc/nginx/conf.d/www.conf <<EOF server { listen ${IP:-0.0.0.0}:${PORT:-80}; server_name $HOSTNAME; root ${NGINX_DIR}; } EOF exec "$@" # 上面说过,CMD和ENTRYPOINT联用时,CMD后面的内容会作为参数传递给ENTRYPOINT后面的程序。此处的"$@"意思是接收entrypoint.sh后面的所有参数(也就是CMD后面的所有内容),exec意思是执行接收到的参数并替换当前进程。 # 添加可执行权限。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# chmod +x entrypoint.sh [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# cat Dockerfile # test dockerfile5 FROM nginx:1.18.0-alpine ARG AUTHOR="merle <merle@freeit.com>" LABEL maintainer=${AUTHOR} ENV NGINX_DIR="/server/sites/test/" ADD index.html ${NGINX_DIR} ADD entrypoint.sh /bin/ CMD [ "nginx", "-g", "daemon off;" ] ENTRYPOINT [ "/bin/entrypoint.sh" ] # 利用此脚本初始化一个配置文件,然后调用脚本中的exec "$@"来执行CMD后面的内容,也就是前台启动nginx。 # 构建。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# docker build --build-arg AUTHOR="onion <onion@freeit.com>" -t nginx:v1 . # 可以看到命令行--build-arg传递的参数覆盖了dockerfile中的AUTOR。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# docker image inspect -f '{{.ContainerConfig.Labels}}' nginx:v1 map[maintainer:onion <onion@freeit.com>] # PS:从处只是以maintainer举例,还可以在构建不同java镜像时传递不通的jar包名等等。
ONBUILD
-
介绍
- 用于在 dockerfile 中定义一个触发器。
- ONBUILD 后面指定的指令在 docker build 时不执行,构建完的镜像在被另一个 dockerfile 文件中 FROM 指令所引用的时才会触发执行。
-
语法格式
ONBUILD <INSTRUCTION>
-
语法说明
- 由于 ONBUILD 不能自我嵌套,且不会触发 FROM 和 MAINTAINER 指令,所以一般情况下使用 RUN 或者 ADD 作为触发指令。
- 在使用 COPY 指令时,应该注意后续引用该镜像的 dockerfile 的同级目录下是否有被拷贝的文件。
-
举例
# 编写dockerfile [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test6 [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test6 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# cp ../test5/* ./ [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# cat Dockerfile # test dockerfile6 FROM nginx:1.18.0-alpine LABEL maintainer="merle <merle@freeit.com>" ENV NGINX_DIR="/server/sites/test/" ADD index.html ${NGINX_DIR} ADD entrypoint.sh /bin/ ONBUILD add http://nginx.org/download/nginx-1.18.0.tar.gz /usr/local/ CMD [ "nginx", "-g", "daemon off;" ] ENTRYPOINT [ "/bin/entrypoint.sh" ] # 构建 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker build -t nginx:v2 . # 启动容器会发现ONBUILD指令并没有执行。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker run -di --name web01 nginx:v2 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker exec -it web01 ls /usr/local/ bin lib share [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker exec -it web01 ps PID USER TIME COMMAND 1 root 0:00 nginx: master process nginx -g daemon off; 8 nginx 0:00 nginx: worker process 27 root 0:00 ps # 现在修改dockerfile,将FROM指向刚刚构建的镜像,并删除ONBUILD指令。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# cat Dockerfile # test dockerfile6 FROM nginx:v2 LABEL maintainer="merle <merle@freeit.com>" ENV NGINX_DIR="/server/sites/test/" ADD index.html ${NGINX_DIR} ADD entrypoint.sh /bin/ CMD [ "nginx", "-g", "daemon off" ] ENTRYPOINT [ "/bin/entrypoint.sh" ] # 构建新镜像。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker build -t nginx:v3 . ... # Executing 1 build trigger Downloading [==================================================>] 1.04MB/1.04MB # 触发执行了nginx:v2中ONBUILD后面的指令。 ... # 此时查看ONBUILD及后面的指令也被执行了。 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker run -di --name web02 nginx:v3 [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker exec -it web02 ls /usr/local/ bin nginx-1.18.0.tar.gz lib share [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker exec -it web02 ps PID USER TIME COMMAND 1 root 0:00 nginx: master process nginx -g daemon off; 9 nginx 0:00 nginx: worker process 16 root 0:00 ps
参考资料
写作不易,转载请注明出处,谢谢~~