一、Dockerfile 简介
Dockerfile 是构建镜像的文本文件,通过一系列指令描述镜像构建过程,构建操作由 Docker daemon 进行,它会先验证语法,然后逐一运行指令,每次生成一个新的镜像层,直到构建出最终的镜像。
Dockerfile 集成了构建镜像所需的全部步骤,从选择基础镜像到运行容器,极大地简化了应用程序的部署和环境配置流程。通过 Dockerfile,开发者可以实现自动化、可重复、一致的构建过程,这对于团队协作和持续集成 / 持续部署流程至关重要。
Docker 镜像是一个特殊的分层文件系统,包含应用程序和必要的依赖环境,但并不包含任何的动态信息。构建一个镜像,实际上就是为镜像中的每一层创建相应的配置。因此,可以把构建的命令语句、参数配置等信息都写入一个脚本中,这样,“docker commit” 命令的无法重复的问题、镜像臃肿的问题就都被解决了。这个脚本就是 Dockerfile。
(一)什么是 Dockerfile?
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。构建一个镜像,就是为镜像中的每一层创建相应的配置,而 Dockerfile 则是将这些配置信息以指令的形式记录下来。Dockerfile 可以使用 “docker build” 命令进行编译。在编译过程中,每一条指令的内容就是描述该层应如何进行构建。当我们需要定制自己额外的需求时,只需要在 Dockerfile 文件的基础上添加或者修改指令,重新生成新的镜像即可。
(二)Dockerfile 示例
下面通过一个简单的示例来演示如何使用 Dockerfile。在这个示例中,将基于 Nginx 的镜像来构建一个新的镜像,并在该镜像中部署一个简单的 Web 网页。
- 创建一个文件 “Dockerfile”。在该文件中输入以下命令。
FROM nginx
RUN echo '<h1>This is a Demo HTML</h1>' >/usr/share/nginx/html/index.html
-
在 Dockerfile 所在的目录下执行 “docker build” 命令构建镜像。构建的过程如下图所示。提示:“docker build” 命令会在当前目录下寻找名为 “Dockerfile” 的文件,然后对该文件进行编译生成镜像。如果文件名不是 Dockerfile,则可以在使用 “docker build” 命令加上 “-f” 参数指定文件名称。
-
查看新生成的镜像,如下图所示。
-
使用新生成的镜像创建容器。
docker run -d -p 7788:80 mynginx
-
使用浏览器访问宿主机的 7788 端口,可以看到如下图所示界面。
(三)Docker File 文件详解
下图展示了 Docker 镜像、容器和 Dockerfile 三者的关系。可以看出使用 Dockerfile 文件定义镜像,然后运行镜像启动容器。下表列出了一个完整的 Dockerfile 文件的组成部分。当完成了 Dockerfile 的编写后,使用 “docker build” 命令将会根据 Dockerfile 中上下文的内容构建新 Docker 镜像。整个构建的过程会被递归处理。因此,如果在 Dockerfile 中含有子路径或 URL 等信息,则它们都将被递归进行构建。
提示:在使用 “docker build” 进程镜像构建时,还可以通过 “-t” 参数指定生成镜像的仓库地址和标签等信息。Dockerfile 构建镜像的过程请参考下图。“docker build” 命令在使用 Dockerfile 生成镜像时,会通过 Docker 的守护进程执行 Dockerfile 中的每一条指令,并在每一步执行完成后生成一个新镜像。当所有的指令执行完成后,会输出最终镜像的 ID。当镜像最终生成后,Docker 的守护进程会自动清理 Docker 的上下文环境,并自动重用已生成的中间镜像,以加速构建的速度。图中方框的部分表明,在构建过程中使用到了 Dockerfile 的缓存机制。
二、Dockerfile 关键字详解
1. FROM 关键字
指定基础镜像,新镜像是基于哪个镜像构建的,建议使用官方镜像作为基础镜像,如 Alpine,体积小且严格控制。在 Dockerfile 中,FROM
指令必须作为第一个指令出现,它为后续的构建步骤提供了基础环境。例如:FROM ubuntu:20.04
,这里指定了以 Ubuntu 20.04 版本的镜像作为基础镜像。
2. MAINTAINER/LABEL 关键字
MAINTAINER
指定维护者信息,例如:MAINTAINER John Doe <john.doe@example.com>
。但按照官方文档描述,MAINTAINER已逐渐过时,目前更推荐使用LABEL来设置元数据。
LABEL
用于给镜像打标签,相对MAINTAINER
更灵活。可以设置多个元数据,一个LABEL是键值对,多个键值对之间使用空格分开,命令换行时使用反斜杠\
。例如:LABEL version="1.0" description="This is a demo image" by="Developer Name"
。
3. RUN 关键字
构建过程中运行的命令,有 shell 格式和 exec 格式两种。
shell 格式:RUN <command>
,命令在 shell 中运行,默认情况下在 Linux 上是/bin/sh -c
,在 Windows 上是cmd /S /C
。例如:RUN apt-get update
。
exec 格式:RUN ["executable", "param1", "param2"]
。例如:RUN ["npm", "install"]
。
4. WORKDIR 关键字
指定进入容器时的目录。使用WORKDIR
可以设置容器内的工作目录,后续的指令将在这个目录下执行。例如:WORKDIR /app
,如果目录不存在,会自动创建。
5. ENV 关键字
设置容器内环境变量,方便访问程序。通过ENV
可以设置环境变量,这些变量在构建过程和容器运行时都有效。例如:ENV NODE_ENV production
,在后续的指令中可以使用这个环境变量,如RUN echo $NODE_ENV
。
6. ADD 关键字
将宿主机资源拷贝进镜像中,会自动解压缩,还能从远程读取资源。ADD
指令可以将宿主机上的文件或目录复制到镜像中。如果源是一个压缩文件,它会自动解压缩。例如:ADD myfile.tar.gz /app
,会将myfile.tar.gz
解压缩到容器的/app目录下。同时,ADD还支持从远程 URL 获取资源,例如:ADD http://example.com/file.txt /app
,会从指定 URL 下载文件并复制到容器的/app目录。
7. COPY 关键字
将宿主机资源拷贝到镜像中,只支持读取构建所在宿主机资源,更透明。COPY
与ADD
功能类似,但不会自动解压缩文件,也不能从远程读取资源。例如:COPY myfile.txt /app
,只会将宿主机上的myfile.txt
复制到容器的/app
目录。Docker 开发者推荐在满足同等功能的情况下,尽量使用COPY
指令,以避免ADD
指令可能带来的意外情况。
8. VOLUME 关键字
挂载数据卷,根据构建出来的镜像运行容器时,默认有构建时挂载信息。VOLUME
指令用于在容器中创建一个数据卷,这个数据卷可以被多个容器共享,并且数据写入不会影响镜像层。例如:VOLUME /data
,在容器运行时,可以将宿主机上的目录挂载到这个数据卷上,实现数据的持久化存储。
9. EXPOSE 关键字
指定运行容器时对外暴露的端口。EXPOSE
指令用于指定容器在运行时要对外暴露的端口。例如:EXPOSE 8080
,这只是声明了容器要暴露的端口,但要让外部能够访问这些端口,还需要在运行容器时使用-p
参数进行端口映射。
10. CMD 关键字
指定启动容器时要执行的命令,只有最后一个会生效,创建容器时指定命令会覆盖 CMD
命令。CMD
指令用于指定容器启动时默认执行的命令和参数。如果 Dockerfile 中有多个CMD指令,只有最后一个会生效。并且,在创建容器时,可以通过命令行参数覆盖CMD指定的命令。例如:CMD ["node", "app.js"]
,如果在运行容器时使用docker run <image> bash
,则会覆盖CMD指定的命令,执行bash命令。
11. ENTRYPOINT 关键字
指定启动容器时要执行的命令,可以追加命令,执行时机同 CMD。ENTRYPOINT
指令用于指定容器启动时要执行的命令,与CMD不同的是,ENTRYPOINT指定的命令不会被docker run
命令行参数覆盖,而是会将这些参数作为附加参数传递给ENTRYPOINT指定的命令。例如:ENTRYPOINT ["node"]
,如果运行容器时使用docker run <image> app.js
,则容器会执行node app.js
。
12. ARG 关键字
定义变量,和写代码时定义变量一样。ARG用于在构建镜像时定义变量,这些变量可以在后续的指令中使用。例如:ARG BUILD_ENV=production
,在RUN指令中可以使用这个变量,如RUN if [ "$BUILD_ENV" = "production" ]; then npm install --production; else npm install; fi
。
13. ONBUILD 关键字
基于父镜像构建新镜像时,父镜像的 ONBUILD 会被触发。ONBUILD
指令用于在构建新镜像时,当新镜像基于包含ONBUILD指令的父镜像构建时,父镜像的ONBUILD指令会被触发执行。例如:ONBUILD RUN echo "This is triggered when building a child image"
,当一个新镜像基于这个镜像构建时,这个命令会被执行。
三、Dockerfile 实战案例
1. 构建 Flask 镜像
使用 Python 和 Flask 构建一个简单的 Web 应用,通过 Dockerfile 构建镜像并启动容器,在网页输入服务器 IP 和端口即可看到输出。
首先创建一个 Flask 应用,以下是一个简单的示例:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from Flask!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
然后编写 Dockerfile:
FROM python:3.10
WORKDIR /app
COPY..
RUN pip install flask
EXPOSE 5000
CMD ["python", "your_flask_app.py"]
在项目目录下执行以下命令构建镜像:
docker build -t flask-app.