Dockerfile详解
什么是 Dockerfile
Dockerfile 是一个文本文件,包含了一系列指令,用于定义如何构建一个 Docker 镜像。通过编写 Dockerfile,开发者可以自动化地创建和配置容器环境,从而确保应用程序在不同环境中具有一致的运行效果。
类似于 Makefile,Dockerfile 提供了一种声明式的方式来描述镜像的构建过程。针对文件中的每一行,Docker 都会逐行解析并执行相应的指令,最终生成一个新的镜像。
在之前,我们讲过使用 docker commit 命令来创建一个新的镜像,这种方式虽然简单,但不够灵活和可重复,docker commit 适合用于临时性的修改和测试,但它不具备版本控制和可追溯性。相比之下,Dockerfile 提供了更强大的功能,使得镜像的构建过程更加透明和可控。
Dockerfile 就像是造房子的蓝图,定义了房子的结构和功能,而 docker commit 则更像是对现有房子进行改造。通过 Dockerfile,开发者可以精确地指定每一步的操作,从而确保最终的镜像符合预期。
为什么需要 Dockerfile
使用 Dockerfile 有以下几个主要优势:
- 可以按照需要自定义镜像,添加所需的软件和配置。
- 官方镜像通常是最小化的,只包含运行特定应用所需的基本组件。通过 Dockerfile,开发者可以根据项目需求添加额外的软件包、库和配置,从而创建一个完全符合需求的镜像。
- 方便自动化构建,重复执行效率高
- Dockerfile 提供了一种声明式的方式来描述镜像的构建过程。通过编写 Dockerfile,开发者可以轻松地自动化镜像的构建过程,确保每次构建都能得到一致的结果。若使用
docker commit,则需要手动执行每一步操作,容易出错且效率低下。 - 通过 Dockerfile,可以将镜像的构建过程版本化,便于团队协作和代码审查。每次修改 Dockerfile 都可以通过版本控制系统进行跟踪,确保所有团队成员都能了解镜像的变化历史。
- Dockerfile 提供了一种声明式的方式来描述镜像的构建过程。通过编写 Dockerfile,开发者可以轻松地自动化镜像的构建过程,确保每次构建都能得到一致的结果。若使用
- 提高可移植性,确保在不同环境中具有一致的运行效果。
- 通过 Dockerfile 定义的镜像可以在任何支持 Docker 的环境中运行,无论是开发环境、测试环境还是生产环境。这种一致性有助于减少“在我机器上能运行”的问题,提高应用程序的可靠性。
- 便于维护和更新,不再是黑箱操作
- Dockerfile 提供了一种清晰的方式来描述镜像的构建过程,使得维护和更新变得更加容易。通过修改 Dockerfile,可以轻松地添加新功能、修复漏洞或更新软件包,而不需要手动操作每个步骤。
- 使用 Dockerfile 可以更好地管理依赖关系,确保镜像中的软件包和库始终保持最新状态,从而提高应用程序的安全性和性能。
Dockerfile 格式
# Comment
INSTRUCTION arguments
- 指令并不区分大小写。但是,惯例上我们通常使用大写字母来书写指令,以提高可读性。
- 注释行以
#开头,Docker 在构建镜像时会忽略这些注释。若将#放在指令的中间或结尾,Docker 会将其视为指令的一部分,而不是注释。 - 每个指令通常占据一行,但可以使用反斜杠
\来将长指令拆分为多行,以提高可读性。
Dockerfile 指令详解
FROM
FROM 指令用于指定基础镜像,是每个 Dockerfile 的第一条指令。它定义了新镜像将基于哪个现有的镜像进行构建。
语法格式:
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
--platform=<platform>:可选参数,用于指定目标平台(如linux/amd64、linux/arm64等)。这在构建多架构镜像时非常有用。<image>:必需参数,指定基础镜像的名称,可以是官方镜像(如ubuntu、alpine)或自定义镜像。[:<tag>]:可选参数,指定镜像的标签(版本)。如果不指定标签,默认使用latest标签。[@<digest>]:可选参数,指定镜像的内容摘要(SHA256)。这确保了使用的镜像是特定版本,而不是标签可能指向的最新版本。[AS <name>]:可选参数,用于为该构建阶段命名,便于在多阶段构建中引用。
示例:
FROM --platform=linux/amd64 busybox:stable-glibc AS mybusybox1
该指令指定了一个基于 busybox 镜像的基础镜像,并命名为 mybusybox1,同时指定了目标平台为 linux/amd64。
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2481]
└─[$] docker build -t mybusybox:v0.1 . [11:11:13]
[+] Building 0.1s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 100B 0.0s
=> WARN: FromPlatformFlagConstDisallowed: FROM --platform flag should not use constant value "linux/amd64" (line 1) 0.0s
=> [internal] load metadata for docker.io/library/busybox:stable-glibc 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/busybox:stable-glibc 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:e546da435f4e22fc7cdba57bf308167dc183435d0e318161bae4f400832c09fc 0.0s
=> => naming to docker.io/library/mybusybox:v0.1 0.0s
1 warning found (use docker --debug to expand):
- FromPlatformFlagConstDisallowed: FROM --platform flag should not use constant value "linux/amd64" (line 1)
LABEL
LABEL 指令用于为镜像添加元数据(标签),这些标签以键值对的形式存储,可以包含关于镜像的信息,如作者、版本、描述等。
语法格式:
LABEL <key>=<value> ...
<key>:必需参数,指定标签的键(名称)。<value>:必需参数,指定标签的值。值可以包含空格,但必须用引号括起来。
示例:
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
该指令为镜像添加了两个标签,分别是 company 和 app,用于描述镜像的所属公司和应用名称。
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2487]
└─[$] docker build -t web1:v0.1 . [11:23:54]
[+] Building 0.1s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 124B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/ubuntu:22.04 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:b742bf889f63f94181793d3431d049884b9530aacb494765a5d628a6f3ce4fdc 0.0s
=> => naming to docker.io/library/web1:v0.1
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2488]
└─[$] docker inspect web1:v0.1
......
"Labels": {
"app": "nginx",
"company": "com.ljx",
"org.opencontainers.image.ref.name": "ubuntu",
"org.opencontainers.image.version": "22.04"
},
......
我们可以看到,在该镜像的元数据中成功添加了 company 和 app 标签。
COPY
COPY 指令用于从 docker 主机系统复制文件或目录到镜像中的指定路径。它是将应用程序代码、配置文件或其他资源添加到镜像中的常用方法。
语法格式:
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
<src>:必需参数,指定要复制的源文件或目录,可以是相对路径或绝对路径。<dest>:必需参数,指定目标路径,即容器内的路径。--chown=<user>:<group>:可选参数,用于设置复制文件的所有者和所属组。可以使用用户名或 UID,组名或 GID。
示例:
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
COPY index.html /data/web/www/
该指令将主机系统中的 index.html 文件复制到镜像内的 /data/web/www/ 目录。
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2497]
└─[$] docker build -t web1:v0.2 . [11:35:33]
[+] Building 0.2s (7/7) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 155B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 31B 0.0s
=> CACHED [1/2] FROM docker.io/library/ubuntu:22.04 0.0s
=> [2/2] COPY index.html /data/web/www/ 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:fef409ca070e3bfd7c1ecb4266cd2ed761e3c8a1121a1a450cc198b45312da56 0.0s
=> => naming to docker.io/library/web1:v0.2 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2498]
└─[$] docker run --name web1 --rm -it web1:v0.2 ls /data/web/www [11:35:40]
index.html
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2499]
└─[$] docker run --name web1 --rm -it web1:v0.2 cat /data/web/www/index.html [11:35:58]
<html>
<h1>Hello ,My Home Page!</h1>
</html>
我们可以看到,index.html 文件已经成功复制到镜像内的指定目录,并且内容正确。
ENV
ENV 指令用于在镜像中设置环境变量,这些变量可以在容器运行时被访问和使用。通过设置环境变量,可以配置应用程序的行为,传递配置信息等。
语法格式:
ENV <key> <value>
<key>:必需参数,指定环境变量的名称。<value>:必需参数,指定环境变量的值。值可以包含空格,但必须用引号括起来。
示例:
若我们需要频繁复用 /data/web/www 目录,可以通过 ENV 指令设置一个环境变量 WEBROOT 来简化路径的使用。
我们依然使用之前的 Dockerfile,在其中添加 ENV 指令:
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2523]
└─[$] docker build -t web1:v0.3 . [16:38:26]
[+] Building 0.1s (7/7) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 182B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 31B 0.0s
=> [1/2] FROM docker.io/library/ubuntu:22.04 0.0s
=> CACHED [2/2] COPY index.html /data/web/www/ 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:6da50af3c550136fa51bb3c29566b71801ced365558fa189e0cbb98a13dc7934 0.0s
=> => naming to docker.io/library/web1:v0.3 0.0s
# 运行容器,验证结果
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2524]
└─[$] docker run --name web1 --rm -it web1:v0.3 cat /data/web/www/index.html [16:38:39]
<html>
<h1>Hello ,My Home Page!</h1>
</html>
我们可以看到,index.html 文件已经成功复制到镜像内的指定目录,并且内容正确。通过这种方式,我们可以更方便地管理和复用路径,提升 Dockerfile 的可读性和维护性。
通过 docker image inspect web1:v0.3 命令,我们还可以看到环境变量 WEB_ROOT 已经成功设置:
"Config": {
"Cmd": [
"/bin/bash"
],
"Entrypoint": null,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"WEB_ROOT=/data/web/www/" # 这里可以看到我们设置的环境变量
],
"Labels": {
"app": "nginx",
"company": "com.ljx",
"org.opencontainers.image.ref.name": "ubuntu",
"org.opencontainers.image.version": "22.04"
},
"OnBuild": null,
"User": "",
"Volumes": null,
"WorkingDir": ""
}
WORKDIR
WORKDIR 指令用于设置工作目录,即在后续的指令中使用的默认目录。如果指定的目录不存在,Docker 会自动创建它。通过设置工作目录,可以简化路径的使用,提高 Dockerfile 的可读性和维护性。
语法格式:
WORKDIR <path>
需要注意的是,WORKDIR 指令可以多次使用,每次使用都会基于上一次设置的目录进行路径解析。如果指定的是绝对路径,则会直接切换到该路径。这就类似于在 Linux 系统中使用 cd 命令切换目录。
示例:
后面我们需要下载 nginx 并解压到 /data/web/www 目录下,如果每次都写完整路径会比较麻烦。我们可以通过 WORKDIR 指令将工作目录切换到 /data/web/www,这样后续的操作就可以直接使用相对路径。
继续使用之前的 Dockerfile,在其中添加 WORKDIR 指令:
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local # 设置工作目录
我们来验证一下启动容器后的默认路径:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2528]
└─[$] docker build -t web1:v0.4 . [16:44:29]
[+] Building 0.2s (8/8) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 201B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 31B 0.0s
=> [1/3] FROM docker.io/library/ubuntu:22.04 0.0s
=> CACHED [2/3] COPY index.html /data/web/www/ 0.0s
=> [3/3] WORKDIR /usr/local 0.1s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => writing image sha256:ff50e3e09dc63e82c9b88ddc73403afa938124dd48ded64f3a99f620d105b11b 0.0s
=> => naming to docker.io/library/web1:v0.4 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2530]
└─[$] docker run --name web1 --rm -it web1:v0.4 pwd [16:44:51]
/usr/local
我们可以看到,容器启动后默认路径已经切换到 /usr/local 目录。
ADD
ADD 指令用于将文件和目录从主机系统复制到镜像中,类似于 COPY 指令,但它具有更多的功能。除了基本的复制功能外,ADD 还支持从 URL 下载文件以及自动解压归档文件(如 .tar、.tar.gz、.zip 等)。
语法格式:
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
<src>:必需参数,指定要复制的源文件或目录,可以是相对路径、绝对路径或 URL。<dest>:必需参数,指定目标路径,即容器内的路径。--chown=<user>:<group>:可选参数,用于设置复制文件的所有者和所属组。可以使用用户名或 UID,组名或 GID。
建议
<dest>目录是一个绝对路径,否则,ADD 指定以WORKDIR为基准的相对路径,一方面不够直观,另一方面也不利于维护。当然,若设置WORKDIR是为了方便后续操作,可以继续使用相对路径。
若路径中有空格,必须使用引号括起来。
示例:
我们需要下载并解压一个 nginx 的压缩包到 /data/web/www 目录下,可以使用 ADD 指令来实现这一操作。
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
这里,我们灵活地利用了之前设置的 WORKDIR,将下载的文件保存到 /usr/local/src 目录下,且使用了环境变量 NGINX_VERSION 来指定 nginx 的版本号,方便后续的维护和更新。
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2536]
└─[$] docker build -t web1:v0.5 . [16:59:22]
[+] Building 0.9s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 295B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 31B 0.0s
=> [1/4] FROM docker.io/library/ubuntu:22.04 0.0s
=> [4/4] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.7s
=> CACHED [2/4] COPY index.html /data/web/www/ 0.0s
=> CACHED [3/4] WORKDIR /usr/local 0.0s
=> CACHED [4/4] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:d7c900cd66f6d6fa42c0312188333ef84cf79f269336764b7891d3bd8922a4d2 0.0s
=> => naming to docker.io/library/web1:v0.5 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2537]
└─[$] docker run --name web1 --rm -it web1:v0.5 ls -l /usr/local/src [16:59:27]
total 1252
-rw------- 1 root root 1280111 Apr 23 11:55 nginx-1.28.0.tar.gz
我们可以看到,nginx-1.28.0.tar.gz 文件已经成功下载到镜像内的 /usr/local/src 目录。
若我们希望在下载后自动解压该文件,可以在宿主机中先将压缩包下载下来,然后使用 ADD 指令将其添加到镜像中,Docker 会自动解压该文件。
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2543]
└─[$] docker build -t web1:v0.6 . [17:03:29]
[+] Building 0.6s (11/11) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 330B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/5] FROM docker.io/library/ubuntu:22.04 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 1.28MB 0.0s
=> [4/5] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.2s
=> CACHED [2/5] COPY index.html /data/web/www/ 0.0s
=> CACHED [3/5] WORKDIR /usr/local 0.0s
=> CACHED [4/5] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.0s
=> [5/5] ADD nginx-1.28.0.tar.gz ./src2 0.2s
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:f8aefdb1c6b83ff1adeab115642367159bd2b7e8e1e9a940f616c8589eb4eb95 0.0s
=> => naming to docker.io/library/web1:v0.6 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2545]
└─[$] docker run --name web1 --rm -it web1:v0.6 ls -l /usr/local/src2 [17:03:43]
total 4
drwxr-xr-x 8 502 dialout 4096 Apr 23 11:55 nginx-1.28.0
我们可以看到,nginx-1.28.0.tar.gz 文件已经成功解压到镜像内的 /usr/local/src2 目录。
RUN
RUN 指令用于在镜像构建过程中执行命令。它通常用于安装软件包、配置环境或执行其他需要在镜像内完成的任务。每个 RUN 指令都会创建一个新的镜像层,因此合理使用 RUN 指令可以优化镜像的大小和构建速度。
语法格式:
# shell form
RUN <command>
# exec form
RUN ["executable", "param1", "param2"]
<command>:必需参数,指定要执行的命令,可以是单个命令或一系列命令。若使用 shell 形式,命令会在/bin/sh -c下执行;若使用 exec 形式,命令会直接执行,不经过 shell。["executable", "param1", "param2"]:这种形式使用 JSON 数组来指定命令和参数,避免了 shell 解析的问题,适合需要传递复杂参数的情况。
示例:
接着之前的 Dockerfile,我们可以使用 RUN 指令来解压 nginx 的压缩包
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2547]
└─[$] docker build -t web1:v0.7 . [17:12:07]
[+] Building 1.9s (12/12) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 379B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/6] FROM docker.io/library/ubuntu:22.04 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 71B 0.0s
=> [4/6] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.8s
=> CACHED [2/6] COPY index.html /data/web/www/ 0.0s
=> CACHED [3/6] WORKDIR /usr/local 0.0s
=> CACHED [4/6] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.0s
=> CACHED [5/6] ADD nginx-1.28.0.tar.gz ./src2 0.0s
=> [6/6] RUN cd ./src && tar zxvf nginx-1.28.0.tar.gz 0.9s
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:0a5409a2fc34ca07e31356fd6823b4fabfd3356b40866b68ae0f095d8630d9de 0.0s
=> => naming to docker.io/library/web1:v0.7 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2548]
└─[$] docker run --name web1 --rm -it web1:v0.7 ls -l src [17:12:15]
total 1256
drwxr-xr-x 8 502 staff 4096 Apr 23 11:55 nginx-1.28.0
-rw------- 1 root root 1280111 Apr 23 11:55 nginx-1.28.0.tar.gz
我们可以看到,nginx-1.28.0.tar.gz 文件已经成功解压到镜像内的 /usr/local/src/nginx-1.28.0 目录。
演示中之所以直接使用 ls -l src 来查看解压结果,是因为 WORKDIR 已经切换到 /usr/local 目录下,所以可以直接使用相对路径 src 来访问该目录。在实际生产中,建议使用绝对路径以避免路径混淆。
解压后,我们需要安装一些必要的依赖包,然后编译安装 nginx,可以继续使用 RUN 指令来完成这些操作:
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#1. install the build-essential build tool
#2. install libpcre3 libpcre3-dev zlib1g-dev (depend on)
#3. enter th nginx dir
#4. compilate and construct
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2550]
└─[$] docker build -t web1:v0.8 . [17:22:12]
[+] Building 77.8s (14/14) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 722B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/8] FROM docker.io/library/ubuntu:22.04 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 71B 0.0s
=> [4/8] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.8s
=> CACHED [2/8] COPY index.html /data/web/www/ 0.0s
=> CACHED [3/8] WORKDIR /usr/local 0.0s
=> CACHED [4/8] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.0s
=> CACHED [5/8] ADD nginx-1.28.0.tar.gz ./src2 0.0s
=> CACHED [6/8] RUN cd ./src && tar zxvf nginx-1.28.0.tar.gz 0.0s
=> [7/8] RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev 46.1s
=> [8/8] RUN cd ./src/nginx-1.28.0 && ./configure --prefix=/usr/local/nginx && make && make install 27.4s
=> exporting to image 3.3s
=> => exporting layers 3.3s
=> => writing image sha256:3239e4dec65dc68d68636673d37dafd80aa9b7bbc6db906e3b72598db7e2fdc9 0.0s
=> => naming to docker.io/library/web1:v0.8 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2551]
└─[$] docker run --name web1 --rm -it web1:v0.8 /usr/local/nginx/sbin/nginx -V [17:23:35]
nginx version: nginx/1.28.0
built by gcc 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04.2)
configure arguments: --prefix=/usr/local/nginx
我们可以看到,nginx 已经成功编译安装到 /usr/local/nginx 目录下。
下面我们给 nginx 添加一个简单的配置文件,指定其默认的网页根目录为 /data/web/www,并启动 nginx 服务(配置文件不做演示,其作用是代理之前导入的 index.html):
我们将配置文件 nginx.conf 放在当前目录下,顺便给 Dockerfile 中的指令顺序进行了一些调整,以优化镜像层的缓存利用率:
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
#1. install the build-essential build tool
#2. install libpcre3 libpcre3-dev zlib1g-dev (depend on)
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
ADD ${NGINX_VERSION}.tar.gz ./src2
#3. enter th nginx dir
#4. compilate and construct
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
缓存优化说明:
- 将安装依赖的
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev指令放在前面,这样如果后续的文件复制或其他操作没有改变,Docker 可以利用缓存,避免重复安装依赖,从而加快构建速度。 - 将
COPY index.html ${WEB_ROOT}放在安装依赖之后,这样如果index.html没有变化,Docker 也可以利用缓存,避免重新复制文件。
总的来说,这种策略就是让不常变化的指令放在前面,常变化的指令放在后面,从而最大化地利用缓存,提高构建效率。
CMD
CMD 指令用于为启动的容器指定默认的命令和参数。它定义了容器启动时要执行的命令,如果在运行容器时没有指定其他命令,Docker 会使用 CMD 指令中定义的命令。每个 Dockerfile 只能有一个 CMD 指令,如果有多个,只有最后一个会生效。
语法格式:
CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
["executable","param1","param2"]:这种形式使用 JSON 数组来指定命令和参数,避免了 shell 解析的问题,适合需要传递复杂参数的情况。["param1","param2"]:这种形式用于为ENTRYPOINT指令指定默认参数。command param1 param2:这种形式使用 shell 语法,命令会在/bin/sh -c下执行,适合简单的命令。
示例:
继续使用之前的 Dockerfile,我们可以添加 CMD 指令来启动 nginx 服务:
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
#1. install the build-essential build tool
#2. install libpcre3 libpcre3-dev zlib1g-dev (depend on)
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
ADD ${NGINX_VERSION}.tar.gz ./src2
#3. enter th nginx dir
#4. compilate and construct
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
CMD ["/usr/local/nginx/sbin/nginx","-c","/usr/local/nginx/conf/nginx.conf","-g","daemon off;"]
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2558]
└─[$] docker build -t web1:v1.0 . [18:25:40]
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2561]
└─[$] docker run --name web1 --rm -it web1:v0.9 /usr/local/nginx/sbin/nginx -V [18:30:41]
nginx version: nginx/1.28.0
built by gcc 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04.2)
configure arguments: --prefix=/usr/local/nginx
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2562]
└─[$] docker run --name web1 --rm -it web1:v1.0 [18:30:49]
# 另一个终端验证
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2563]
└─[$] docker ps | grep web1 [18:34:11]
fd195d04d3fd web1:v1.0 "/usr/local/nginx/sb…" 2 minutes ago Up 2 minutes web1
我们可以看到,nginx 服务已经成功启动,并且容器在运行中。通过 docker ps 命令可以验证容器的状态。
EXPOSE
EXPOSE 指令用于向外界声明容器内应用程序所监听的端口。它并不会实际打开端口或进行端口映射,而是作为一种文档化的方式,告诉用户和其他开发者该容器需要哪些端口才能正常工作。通常与 docker run -p 或 docker run -P 命令结合使用,以实现端口映射。
语法格式:
EXPOSE <port> [<port>/<protocol>...]
<port>:必需参数,指定要暴露的端口号,可以是单个端口或多个端口。<protocol>:可选参数,指定端口使用的协议,默认为 TCP。可以是tcp或udp。
示例:
继续使用之前的 Dockerfile,我们可以添加 EXPOSE 指令来声明 nginx 所监听的端口(默认是 80):
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
#1. install the build-essential build tool
#2. install libpcre3 libpcre3-dev zlib1g-dev (depend on)
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
ADD ${NGINX_VERSION}.tar.gz ./src2
#3. enter th nginx dir
#4. compilate and construct
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
EXPOSE 80/tcp # 声明容器内 nginx 监听的端口
CMD ["/usr/local/nginx/sbin/nginx","-c","/usr/local/nginx/conf/nginx.conf","-g","daemon off;"]
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2603]
└─[$] docker build -t web1:v1.1 . [19:32:58]
[+] Building 0.5s (16/16) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 848B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [ 1/10] FROM docker.io/library/ubuntu:22.04 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 378B 0.0s
=> [ 6/10] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.2s
=> CACHED [ 2/10] COPY index.html /data/web/www/ 0.0s
=> CACHED [ 3/10] RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev 0.0s
=> CACHED [ 4/10] COPY index.html /data/web/www/ 0.0s
=> CACHED [ 5/10] WORKDIR /usr/local 0.0s
=> CACHED [ 6/10] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.0s
=> CACHED [ 7/10] RUN cd ./src && tar zxvf nginx-1.28.0.tar.gz 0.0s
=> CACHED [ 8/10] ADD nginx-1.28.0.tar.gz ./src2 0.0s
=> CACHED [ 9/10] RUN cd ./src/nginx-1.28.0 && ./configure --prefix=/usr/local/nginx && make && make install 0.0s
=> [10/10] COPY nginx.conf ./nginx/conf/ 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:00ef6fa8b9b8683082fb1aab7226b754b4ef8aaef7d233641abcdba8ae8270a4 0.0s
=> => naming to docker.io/library/web1:v1.1 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2604]
└─[$] docker run --name web1 --rm -it -p 8088:80 web1:v1.1
访问浏览器 http://<你的服务器IP>:8088,可以看到之前添加的 index.html 页面;

ENTRYPOINT
ENTRYPOINT 指令用于为容器指定一个默认的可执行程序,使得容器可以像一个独立的应用程序一样运行。与 CMD 指令不同,ENTRYPOINT 指令定义的命令不会被 docker run 命令行参数覆盖,而是作为容器启动时的主命令执行。通常与 CMD 指令结合使用,以提供默认参数。
语法格式:
ENTRYPOINT ["executable", "param1", "param2"] (exec form, this is the preferred form)
ENTRYPOINT command param1 param2 (shell form)
["executable", "param1", "param2"]:这种形式使用 JSON 数组来指定命令和参数,避免了 shell 解析的问题,适合需要传递复杂参数的情况。command param1 param2:这种形式使用 shell 语法,命令会在/bin/sh -c下执行,适合简单的命令。
示例:
继续使用之前的 Dockerfile,我们可以将 CMD 指令替换为 ENTRYPOINT 指令来启动 nginx 服务:
# my webside by ljx
FROM ubuntu:22.04 AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
COPY index.html ${WEB_ROOT}
#1. install the build-essential build tool
#2. install libpcre3 libpcre3-dev zlib1g-dev (depend on)
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
ADD ${NGINX_VERSION}.tar.gz ./src2
#3. enter th nginx dir
#4. compilate and construct
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
EXPOSE 80/tcp
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
运行后效果与之前的 CMD 指令类似,nginx 服务会自动启动,并且容器在运行中,这里就不再赘述。
ARG
ARG 指令用于定义构建时的变量,这些变量可以在构建过程中使用,但不会保存在最终的镜像中。它们通常用于传递构建参数,例如软件版本号、环境配置等。与 ENV 指令不同,ARG 定义的变量只能在构建阶段使用,不能在运行时访问。
翻译过来就是,ARG 指令可以在构建 Docker 镜像时传递参数,这些参数在构建完成后不会保存在镜像中,因此在运行容器时无法访问这些参数。而 ENV 指令定义的环境变量会保存在镜像中,并且在运行容器时可以访问,但 ENV 定义的变量不能在构建阶段使用。
语法格式:
ARG <name>[=<default value>]
<name>:必需参数,指定变量的名称,必须是有效的环境变量名称。=<default value>:可选参数,指定变量的默认值,如果在构建时没有提供值,则使用默认值。ARG指令必须在FROM指令之前定义,才能在FROM指令中使用这些变量。
在构建阶段,可以使用 --build-arg <name>=<value> 选项来传递参数值。
示例:
有一天,老板突然要求我们将 ubuntu 的版本从 22.04 升级到 24.04,我们可以使用 ARG 指令来定义一个变量 UBUNTU_VERSION,并在 FROM 指令中引用该变量:
# my webside by ljx
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION} AS buildbase
LABEL company="com.ljx" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.28.0"
COPY index.html ${WEB_ROOT}
#1. install the build-essential build tool
#2. install libpcre3 libpcre3-dev zlib1g-dev (depend on)
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
ADD ${NGINX_VERSION}.tar.gz ./src2
#3. enter th nginx dir
#4. compilate and construct
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
EXPOSE 80/tcp
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2611]
└─[$] docker build --build-arg UBUNTU_VERSION=24.04 -t web1:v1.3 . [20:17:10]
[+] Building 95.1s (16/16) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 892B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:24.04 1.1s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [ 1/10] FROM docker.io/library/ubuntu:24.04@sha256:9cbed754112939e914291337b5e554b07ad7c392491dba6daf25eef1332a22e8 2.3s
=> => resolve docker.io/library/ubuntu:24.04@sha256:9cbed754112939e914291337b5e554b07ad7c392491dba6daf25eef1332a22e8 0.0s
=> => sha256:9cbed754112939e914291337b5e554b07ad7c392491dba6daf25eef1332a22e8 6.69kB / 6.69kB 0.0s
=> => sha256:3f83fb03282ef4e453bdf0060e0d83833bb3cf6e6f36f54d9b8517d311d78e03 424B / 424B 0.0s
=> => sha256:802541663949fbd5bbd8f35045af10005f51885164e798e2ee8d1dc39ed8888d 2.29kB / 2.29kB 0.0s
=> => sha256:76249c7cd50397d2e8c06a75106723d057deaba0ffbc7f4af1bb02bcf71d81cf 29.72MB / 29.72MB 1.0s
=> => extracting sha256:76249c7cd50397d2e8c06a75106723d057deaba0ffbc7f4af1bb02bcf71d81cf 1.2s
=> [internal] load build context 0.0s
=> => transferring context: 101B 0.0s
=> CACHED [ 6/10] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.7s
=> [ 2/10] COPY index.html /data/web/www/ 0.1s
=> [ 3/10] RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev 57.1s
=> [ 4/10] COPY index.html /data/web/www/ 0.1s
=> [ 5/10] WORKDIR /usr/local 0.1s
=> [ 6/10] ADD https://nginx.org/download/nginx-1.28.0.tar.gz ./src 0.1s
=> [ 7/10] RUN cd ./src && tar zxvf nginx-1.28.0.tar.gz 0.6s
=> [ 8/10] ADD nginx-1.28.0.tar.gz ./src2 0.2s
=> [ 9/10] RUN cd ./src/nginx-1.28.0 && ./configure --prefix=/usr/local/nginx && make && make install 28.8s
=> [10/10] COPY nginx.conf ./nginx/conf/ 0.2s
=> exporting to image 4.3s
=> => exporting layers 4.3s
=> => writing image sha256:b336716a999b979dcefcab5046b6e45c5d0e6e9a1f42ec4b1a327680f3aea6dc 0.0s
=> => naming to docker.io/library/web1:v1.3 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2616]
└─[$] docker run --name web1 --rm -it -p 8088:80 --entrypoint cat web1:v1.3 /etc/*release* [20:25:01]
PRETTY_NAME="Ubuntu 24.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.3 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
我们可以看到,镜像内的操作系统已经成功升级到 Ubuntu 24.04。需要注意的是,这里只是为了验证版本升级,实际生产中尽量不要使用 --entrypoint 来覆盖入口点。
VOLUME
VOLUME 指令用于在 Docker 镜像中创建一个或多个挂载点(卷),这些挂载点可以用来存储和共享数据。通过 VOLUME 指令定义的卷在容器运行时会被挂载到宿主机的文件系统中,从而实现数据的持久化和共享。与 COPY 和 ADD 指令不同,VOLUME 指令不会将数据复制到镜像中,而是创建一个独立的存储区域。
语法格式:
VOLUME <mountpoint> [<mountpoint>...]
<mountpoint>:必需参数,指定要创建的挂载点,可以是单个路径或多个路径。路径必须是绝对路径。
该选项既可以创建出一个匿名卷,也可以创建一个具名卷,具体取决于在运行容器时是否使用 -v 或 --mount 选项来指定卷的名称。
示例:
继续使用之前的 Dockerfile,我们可以添加 VOLUME 指令来创建一个挂载点 /data,用于存储网站数据:
# my webside by ljx
FROM ubuntu:24.04 AS buildbase
LABEL company="com.ljx" app="nginx"
VOLUME /data
若在运行容器时没有指定卷名称,则 Docker 会创建一个匿名卷:
docker run -d --name web1 -p 8088:80 web1:v1.3
若在运行容器时指定了卷名称,则 Docker 会创建一个具名卷:
docker run -d --name web1 -p 8088:80 -v mydata:/data web1:v1.3
这里不做过多演示,主要是说明 VOLUME 指令的作用和用法。
SHELL
SHELL 指令用于在 Dockerfile 中指定后续 RUN 指令所使用的默认 shell。默认情况下,Docker 使用 /bin/sh -c 作为 shell,但通过 SHELL 指令可以更改为其他 shell,例如 /bin/bash。这对于需要使用特定 shell 功能或语法的命令非常有用。
·语法格式:
SHELL ["executable", "parameters"]
["executable", "parameters"]:必需参数,使用 JSON 数组来指定要使用的 shell 及其参数。executable是 shell 的路径,parameters是传递给 shell 的参数。
SHELL 指令可以多次使用,每次使用都会覆盖之前的设置,影响后续的 RUN 指令。
示例:
USER
USER 指令用于指定运行容器时的用户和用户组。默认情况下,容器以 root 用户身份运行,但通过 USER 指令可以切换到其他用户,以提高安全性和权限控制。指定的用户必须在镜像中存在,否则会导致容器启动失败。
语法格式:
USER <username>[:<groupname>]
USER <UID>[:<GID>]
<username>:必需参数,指定要使用的用户名。<groupname>:可选参数,指定要使用的用户组名。如果未指定,默认使用用户的主组。<UID>:必需参数,指定要使用的用户 ID。<GID>:可选参数,指定要使用的用户组 ID。如果未指定,默认使用用户的主组 ID。
USER 指令可以多次使用,每次使用都会覆盖之前的设置,影响后续的 RUN 指令。
示例:
FROM ubuntu:22.04 AS buildbase
RUN groupadd nginx
RUN useradd -g nginx nginx
USER nginx:nginx
RUN whoami > /tmp/user1.txt
USER root:root
RUN groupadd mysql
RUN useradd -g mysql mysql
USER mysql:mysql
RUN whoami > /tmp/user2.txt
该示例中,我们创建了两个用户 nginx 和 mysql,并在不同的用户身份下执行了 whoami 命令,将结果分别写入 /tmp/user1.txt 和 /tmp/user2.txt 文件中。
运行如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2639]
└─[$] docker build -f Dockerfile3 -t user:v0.1 . [10:09:09]
[+] Building 0.1s (11/11) FINISHED docker:default
=> [internal] load build definition from Dockerfile3 0.0s
=> => transferring dockerfile: 267B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/7] FROM docker.io/library/ubuntu:22.04 0.0s
=> CACHED [2/7] RUN groupadd nginx 0.0s
=> CACHED [3/7] RUN useradd -g nginx nginx 0.0s
=> CACHED [4/7] RUN whoami > /tmp/user1.txt 0.0s
=> CACHED [5/7] RUN groupadd mysql 0.0s
=> CACHED [6/7] RUN useradd -g mysql mysql 0.0s
=> CACHED [7/7] RUN whoami > /tmp/user2.txt 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:42881bf23e440cbb9c5f072b07c8a71a0d019cfc13bef4618e1605e7727d29a7 0.0s
=> => naming to docker.io/library/user:v0.1 0.0s
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2641]
└─[$] docker run --name user1 -it --rm user:v0.1 bash -c "cd /tmp; cat ./user1.txt; cat ./user2.txt" [10:13:36]
nginx
mysql
HEALTHCHECK
HEALTHCHECK 指令用于为容器定义健康检查命令,以便 Docker 可以定期检查容器内应用程序的状态。通过健康检查,可以确保容器内的服务正常运行,并在出现问题时采取相应的措施,例如重启容器或发送警报。健康检查命令可以是任何返回状态码的命令,通常使用 curl 或 wget 来检查 HTTP 服务的可用性。
语法格式:
HEALTHCHECK [OPTIONS] CMD command (check container health by running a command inside the container)
HEALTHCHECK NONE (disable any healthcheck inherited from the base image)
OPTIONS:可选参数,用于指定健康检查的配置选项,包括:--interval=DURATION:指定健康检查的时间间隔,默认值为 30 秒。--timeout=DURATION:指定健康检查命令的超时时间,默认值为 30 秒。--start-period=DURATION:指定容器启动后等待多长时间才开始进行健康检查,默认值为 0 秒。--retries=N:指定连续失败多少次后将容器标记为不健康,默认值为 3 次。
CMD command:必需参数,指定用于检查容器健康状态的命令。命令应返回 0 表示健康,返回非 0 表示不健康。NONE:用于禁用继承自基础镜像的任何健康检查。
示例:
下面我将通过两个示例来说明 HEALTHCHECK 指令的用法,第一个示例会显示健康检查成功,第二个示例会显示健康检查失败。
FROM nginx:1.22.1
#更换国内镜像源
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
我们通过 docker ps 命令可以看到容器的健康状态:
╭─ljx@VM-16-15-debian ~
╰─➤ docker ps | grep healthcheck
ccd212f63d4d healthcheck:v0.1 "/docker-entrypoint.…" 28 seconds ago Up 28 seconds (healthy) 80/tcp hc1
若将端口号修改为一个错误的端口号,则健康检查会失败:
╭─ljx@VM-16-15-debian ~
╰─➤ docker ps | grep healthcheck
c42c51444faa healthcheck:v0.2 "/docker-entrypoint.…" 15 seconds ago Up 15 seconds (unhealthy) 80/tcp hc1
ONBUILD
ONBUILD 指令用于在构建镜像时为后续的 Dockerfile 指令设置触发器(trigger)。当使用该镜像作为基础镜像时,ONBUILD 指令会在构建过程中自动执行,从而实现一些预定义的操作。通常用于创建可复用的基础镜像,以便在子镜像中自动执行特定的构建步骤。
语法格式:
ONBUILD <INSTRUCTION>
<INSTRUCTION>:必需参数,指定要在子镜像构建时触发执行的 Dockerfile 指令,例如RUN、COPY、ADD等。
我们构建两个镜像,其中一个作为基础镜像,另一个作为子镜像来演示 ONBUILD 指令的作用。
FROM ubuntu:22.04
ONBUILD RUN echo "This will be executed in the child image"
FROM baseimage:v0.1
RUN echo "This is the child image"
当我们构建子镜像时,ONBUILD 指令会触发执行,输出如下:
┌─[root@VM-16-15-debian] - [/home/ljx/docker_test/dockfile_test] - [2648]
└─[$] docker build -f Dockerfile6 -t childimage:v0.1 . [10:37:04]
[+] Building 0.8s (7/7) FINISHED docker:default
=> [internal] load build definition from Dockerfile6 0.0s
=> => transferring dockerfile: 92B 0.0s
=> [internal] load metadata for docker.io/library/baseimage:v0.1 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/2] FROM docker.io/library/baseimage:v0.1 0.0s
=> [2/3] ONBUILD RUN echo "This will be executed in the child image" 0.2s
=> [3/3] RUN echo "This is the child image" 0.3s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:7651baa7f782bfc324b2dac5dd69ebed9ce1b8b3d6102da9356e71fbb9035adb 0.0s
=> => naming to docker.io/library/childimage:v0.1 0.0s
可以注意到,ONBUILD 指令中的 RUN 命令在子镜像构建时被触发执行,输出了 “This will be executed in the child image”。
STOPSIGNAL
STOPSIGNAL 指令用于指定发送给容器内主进程的信号,以便在停止容器时正确地终止应用程序。默认情况下,Docker 使用 SIGTERM 信号来停止容器,但有些应用程序可能需要不同的信号来进行优雅关闭。通过 STOPSIGNAL 指令,可以为这些应用程序指定合适的信号。
语法格式:
STOPSIGNAL signal
| 代号 | 名称 | 内容 |
|---|---|---|
| 1 | SIGHUP | 终端挂断信号,通常用来通知进程重新读取配置或重启(等同“软重启”)。 |
| 2 | SIGINT | 用户中断(通常由 Ctrl+C 触发),请求进程停止运行,可被捕获处理。 |
| 9 | SIGKILL | 强制终止进程,无法被捕获或忽略;若在中间终止,可能留下未完成的临时文件(如 vim 的 .filename.swp)。 |
| 15 | SIGTERM | 请求进程优雅终止(可被捕获以清理资源后退出);若进程无响应则无效。 |
| 19 | SIGSTOP | 暂停/停止进程(可由 SIGCONT 恢复),等同于在终端使用 Ctrl+Z。 |
当我们运行 docker stop 命令时,Docker 会向容器内的主进程发送指定的信号,以便应用程序能够正确地处理停止请求。实验现象不明显,这里就不做演示。
不过大家可以尝试一下,如果将 STOPSIGNAL 设置为 SIGKILL,那么在运行 docker stop 命令时,容器会立即被强制终止,而不会有任何优雅关闭的过程。
Dockerfile 编写技巧
学会了使用各种指令后(这远远不够),我们还需要掌握一些编写 Dockerfile 的技巧,以提高镜像的构建效率和质量。
在编写 Dockerfile 时,既要保证镜像构建的高效性,也要关注镜像的体积、可维护性和可移植性。以下几点是常见且有效的优化策略:
使用
.dockerignore文件- 忽略构建过程中无关的文件(如
.git、日志、测试文件等),减少构建上下文的大小,加快镜像构建速度。
- 忽略构建过程中无关的文件(如
采用多阶段构建(Multi-stage build)
- 在前期阶段完成编译、打包,在最终阶段仅保留运行所需文件。
- 避免将编译工具、构建依赖带入最终镜像,从而得到精简、安全的运行镜像。
合理利用缓存机制
- 将变化频率低的指令放在前面(如依赖安装),变化频率高的放在后面(如代码拷贝)。
- 这样可以最大化地复用缓存层,减少重复构建时间。
选择合适的基础镜像
- 优先使用官方镜像,保证安全性与长期维护性。
- 根据场景选择轻量化镜像(如
alpine、debian:slim、busybox),避免使用过大的基础镜像(如完整的ubuntu),降低最终镜像体积。
减少镜像层数
- 合并多个
RUN、COPY、ADD指令,避免产生过多层。 - 示例:
RUN apt-get update && apt-get install -y curl vim && rm -rf /var/lib/apt/lists/*。
- 合并多个
保证镜像用途单一
- 每个镜像应专注于一个服务或功能,避免“万能镜像”带来复杂性与维护难度。
固定外部依赖版本
- 下载外部数据或依赖时,指定版本和固定地址,确保构建过程可重复,避免因外部源变动而导致构建失败或结果不一致。
只安装必要的软件包
- 避免安装无关的调试工具或多余软件,减少镜像体积与潜在安全风险。
优秀的 Dockerfile 应该 构建快、体积小、安全稳定、用途单一且可复用。通过 .dockerignore、多阶段构建、缓存优化、轻量基础镜像、减少层数、明确用途、固定依赖和精简安装,可以大幅提升 Docker 镜像的质量与可维护性。
上面这些技巧非常重要,直接影响到镜像的构建效率和运行效果,建议大家在实际编写 Dockerfile 时多加注意。下面我将利用一些具体的例子来演示这些技巧的应用。
C++ 镜像制作
首先,我们制作一个最最最最基础的 C++ 镜像,用于完成 C++ 程序的编译和运行,最终实现效果就是打印一句 “Hello, Dockerfile!”。
# 指定基础镜像
FROM ubuntu:22.04
# 设置版本
ENV VERSION=1.0
# 替换国内源
RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
## 切换为 http (刚初始化的系统没有 CA 证书,需要切换为 http后再更新 CA 证书,最后再切换回 https)
# 切换为 http
RUN sed -i 's/https:/http:/g' /etc/apt/sources.list
# 更新 CA 证书
RUN apt-get update && apt-get install -y ca-certificates
# 切换回 https
RUN sed -i 's/http:/https:/g' /etc/apt/sources.list
# 再次更新软件包列表
RUN apt-get update
# 设置工作目录
WORKDIR /src
# 拷贝源文件
COPY demo.cpp .
# 安装 编译工具
RUN apt-get install -y build-essential
# 编译源文件
RUN g++ demo.cpp -o demo
# 运行程序
CMD ["./demo"]
经过编译和运行,我们可以看到输出结果正常:
╭─ljx@VM-16-15-debian ~/docker_test/dockfile_test
╰─➤ docker build -t cpp:v0.1 -f Dockerfile8 .
[+] Building 103.1s (15/15) FINISHED docker:default
=> [internal] load build definition from Dockerfile8 0.0s
=> => transferring dockerfile: 806B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [ 1/10] FROM docker.io/library/ubuntu:22.04 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 136B 0.0s
=> [ 2/10] RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 0.6s
=> [ 3/10] RUN sed -i 's/https:/http:/g' /etc/apt/sources.list 0.3s
=> [ 4/10] RUN apt-get update && apt-get install -y ca-certificates 17.0s
=> [ 5/10] RUN sed -i 's/http:/https:/g' /etc/apt/sources.list 0.4s
=> [ 6/10] RUN apt-get update 4.1s
=> [ 7/10] WORKDIR /src 0.0s
=> [ 8/10] COPY demo.cpp . 0.1s
=> [ 9/10] RUN apt-get install -y build-essential 76.3s
=> [10/10] RUN g++ demo.cpp -o demo 0.9s
=> exporting to image 3.2s
=> => exporting layers 3.1s
=> => writing image sha256:fe69b7d268b2155104443eca4fed6a4d3d51df5ad27c1822f2bf9d8072f51081 0.0s
=> => naming to docker.io/library/cpp:v0.1 0.0s
╭─ljx@VM-16-15-debian ~/docker_test/dockfile_test
╰─➤ docker run --name cpp1 --rm cpp:v0.1 130 ↵
Hello, Dockerfile!
在这个示例中,我们的确做到了对缓存的合理利用(将稳定的层尽可能地放在前面),但是:
请一定一定要注意,这是一个错误示范,我们在编写的时候不应该按照这种随心所欲的方式去编写 Dockerfile,因为这样会导致镜像体积过大,构建时间过长,且不易维护。下面我将通过优化这个 Dockerfile 来展示一些编写技巧。
优化 C++ 镜像制作
针对这个镜像,第一步需要被优化的是 RUN 指令过多,导致镜像层数过多。我们可以将多个 RUN 指令合并为一个 RUN 指令,从而减少镜像层数。
# 指定基础镜像
FROM ubuntu:22.04
# 设置版本
ENV VERSION=1.0
# 替换国内源 并 切换为 http (刚初始化的系统没有 CA 证书,需要切换为 http后再更新 CA 证书,最后再切换回 https) 并安装编译工具
RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
sed -i 's/https:/http:/g' /etc/apt/sources.list && \
apt-get update && apt-get install -y ca-certificates && \
sed -i 's/http:/https:/g' /etc/apt/sources.list && \
apt-get update && \
apt-get install -y build-essential
# 设置工作目录
WORKDIR /src
# 拷贝源文件
COPY demo.cpp .
# 编译源文件
RUN g++ demo.cpp -o demo
# 运行程序
CMD ["./demo"]
经过优化后,镜像层数减少了,构建时间也缩短了:
╭─ljx@VM-16-15-debian ~/docker_test/dockfile_test
╰─➤ docker build -t cpp:v0.1 -f Dockerfile8 .
[+] Building 58.5s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile8 0.0s
=> => transferring dockerfile: 746B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/5] FROM docker.io/library/ubuntu:22.04 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 29B 0.0s
=> [2/5] RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && sed -i 's/https:/http:/g' /etc/apt/sources.list && apt-get update && apt-get install -y c 54.1s
=> [3/5] WORKDIR /src 0.1s
=> [4/5] COPY demo.cpp . 0.1s
=> [5/5] RUN g++ demo.cpp -o demo 0.7s
=> exporting to image 3.3s
=> => exporting layers 3.3s
=> => writing image sha256:047510d43b4f3ef6fdbec204e1c44837b92bde30029f0416e5e764b8da6a4e84 0.0s
=> => naming to docker.io/library/cpp:v0.1 0.0s
但是我们会发现这个镜像所需要的空间非常之大,我们只是想运行一个非常简单的 C++ 程序,却需要安装一个这么大的 ubuntu 系统以及编译环境,这显然是不合理的,因此我们可以采用多阶段构建的方式来优化这个镜像。
# 指定基础镜像
FROM ubuntu:22.04 AS buildbase
# 设置版本
ENV VERSION=1.0
# 替换国内源 并 切换为 http (刚初始化的系统没有 CA 证书,需要切换为 http后再更新 CA 证书,最后再切换回 https) 并安装编译工具
RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
sed -i 's/https:/http:/g' /etc/apt/sources.list && \
apt-get update && apt-get install -y ca-certificates && \
sed -i 's/http:/https:/g' /etc/apt/sources.list && \
apt-get update && \
apt-get install -y build-essential
# 设置工作目录
WORKDIR /src
# 拷贝源文件
COPY demo.cpp .
# 编译源文件
RUN g++ demo.cpp -o demo
# 运行程序
CMD ["./demo"]
# 指定基础镜像
FROM busybox:1.35.0-uclibc
# 设置版本
ENV VERSION=1.0
# 从编译阶段拷贝编译好的文件
COPY --from=buildbase /src/demo /demo
# 运行程序
CMD ["/demo"]
运行如下:
╭─ljx@VM-16-15-debian ~/docker_test/dockfile_test
╰─➤ docker build -t cpp:v0.2 -f Dockerfile8 . 130 ↵
[+] Building 2.2s (13/13) FINISHED docker:default
=> [internal] load build definition from Dockerfile8 0.0s
=> => transferring dockerfile: 948B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [internal] load metadata for docker.io/library/busybox:1.35.0-uclibc 1.3s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [buildbase 1/5] FROM docker.io/library/ubuntu:22.04 0.0s
=> [stage-1 1/2] FROM docker.io/library/busybox:1.35.0-uclibc@sha256:e5877866756a3714cd11309f0b34e7c2c62406fdbfcae39c96beabb5dd4005a7 0.6s
=> => resolve docker.io/library/busybox:1.35.0-uclibc@sha256:e5877866756a3714cd11309f0b34e7c2c62406fdbfcae39c96beabb5dd4005a7 0.0s
=> => sha256:e5877866756a3714cd11309f0b34e7c2c62406fdbfcae39c96beabb5dd4005a7 6.25kB / 6.25kB 0.0s
=> => sha256:32ff5b1723bccebc23986131ae9ca52ef7f4122729df7c325ee3b9505803ba6c 610B / 610B 0.0s
=> => sha256:14c07ec3b135acf9f9a7df4218ba89bafe7db3a91373902eca58352a3a27450d 394B / 394B 0.0s
=> => sha256:641a948ab5969598960cdef2e43df7bef17375945c1d9de0ca15397163394325 749.74kB / 749.74kB 0.4s
=> => extracting sha256:641a948ab5969598960cdef2e43df7bef17375945c1d9de0ca15397163394325 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 29B 0.0s
=> CACHED [buildbase 2/5] RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && sed -i 's/https:/http:/g' /etc/apt/sources.list && apt-get update && apt- 0.0s
=> CACHED [buildbase 3/5] WORKDIR /src 0.0s
=> CACHED [buildbase 4/5] COPY demo.cpp . 0.0s
=> CACHED [buildbase 5/5] RUN g++ demo.cpp -o demo 0.0s
=> [stage-1 2/2] COPY --from=buildbase /src/demo /demo 0.1s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => writing image sha256:6cb3aeed1c8f16f10b378f2a2a2071385c3b81068233d24aec68593b4f257bf2 0.0s
=> => naming to docker.io/library/cpp:v0.2 0.0s
需要注意的是,这里的时间是没有参考意义的,因为 buildbase 部分已经被缓存了,实际构建时间会更长一些。我们可以看到,最终的镜像体积大大减小了:
╭─ljx@VM-16-15-debian ~/docker_test/dockfile_test
╰─➤ docker images | grep cpp
cpp v0.2 6cb3aeed1c8f 57 seconds ago 1.25MB
cpp v0.1 047510d43b4f 6 minutes ago 424MB
经过优化后,镜像体积从 424MB 减少到了 1.25MB,极大地提升了镜像的轻量化程度。
CMD 与 ENTRYPOINT 的使用
在 Dockerfile 中,ENTRYPOINT 和 CMD 都用于指定容器启动时要执行的命令,但它们有一些关键区别和搭配方式。
1. 基本作用
ENTRYPOINT
- 用来定义容器启动时的固定执行命令。
- 一般用于指定容器的主要功能。
- 用户在
docker run时提供的命令会作为 参数 传递给ENTRYPOINT。
CMD
- 提供默认命令或默认参数。
- 如果用户在
docker run时指定了命令,会覆盖CMD。 - 单独存在时也能定义入口,但更常见的用途是给
ENTRYPOINT提供参数。
2. 覆盖规则
在 Dockerfile 内部:新的
ENTRYPOINT或CMD会覆盖前面定义的同类指令。在 容器运行时:
- 如果 Dockerfile 使用了
CMD,用户docker run <image> <command>会直接覆盖CMD。 - 如果 Dockerfile 使用了
ENTRYPOINT,用户输入会变成参数,除非用--entrypoint选项替换掉。
- 如果 Dockerfile 使用了
3. 语法形式
两者都支持 exec 表示法(推荐) 和 shell 表示法:
Exec 语法(推荐,PID=1,能正确接收信号)
CMD ["executable", "param1", "param2"] ENTRYPOINT ["executable", "param1", "param2"]Shell 语法(不推荐,会套一层
/bin/sh -c,信号处理不安全)CMD command param1 param2 ENTRYPOINT command param1 param2
4. 推荐实践
- 固定执行程序:用
ENTRYPOINT,防止被用户随意覆盖。 - 提供默认参数:用
CMD,允许用户运行时修改。 - 总是用 exec 语法,保证 PID=1,容器能正确处理信号退出。
5. 组合模式
常见写法是:
ENTRYPOINT定义主命令,CMD提供默认参数。例如:
ENTRYPOINT ["nginx"] CMD ["-g", "daemon off;"]默认运行:
nginx -g "daemon off;"用户可以修改参数:
docker run mynginx -T最终执行:
nginx -T
总结如下:
- ENTRYPOINT = 程序入口(固定的主命令)。
- CMD = 默认参数(或备用命令)。
- 推荐 组合使用:ENTRYPOINT 固定主功能,CMD 提供默认参数。
- 始终使用 exec 语法,避免信号转发问题。
利用 EntryPoint 和 Cmd 的组合,我们可以实现一个自由 ping 的镜像。
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y iputils-ping
ENTRYPOINT ["/bin/ping", "-c", "3"]
CMD ["localhost"]
运行如下:
╭─ljx@VM-16-15-debian ~/docker_test/dockfile_test
╰─➤ docker run --name ping --rm ping:1.0 130 ↵
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.032 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.044 ms
64 bytes from localhost (::1): icmp_seq=3 ttl=64 time=0.033 ms
--- localhost ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2042ms
rtt min/avg/max/mdev = 0.032/0.036/0.044/0.005 ms
╭─ljx@VM-16-15-debian ~/docker_test/dockfile_test
╰─➤ docker run --name ping --rm ping:1.0 www.baidu.com
PING www.a.shifen.com (110.242.69.21) 56(84) bytes of data.
64 bytes from 110.242.69.21 (110.242.69.21): icmp_seq=1 ttl=250 time=16.6 ms
64 bytes from 110.242.69.21 (110.242.69.21): icmp_seq=2 ttl=250 time=16.6 ms
64 bytes from 110.242.69.21 (110.242.69.21): icmp_seq=3 ttl=250 time=16.6 ms
--- www.a.shifen.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 16.618/16.629/16.644/0.010 ms
ignore 文件使用
在构建 Docker 镜像时,Docker 会将当前目录下的所有文件和子目录发送到 Docker 守护进程作为构建上下文。如果上下文中包含不必要的文件(如日志文件、临时文件、版本控制目录等),不仅会增加构建时间,还会导致镜像体积变大。为了解决这个问题,可以使用 .dockerignore 文件来指定哪些文件和目录应该被忽略,从而优化构建过程。
.dockerignore 文件的语法类似于 .gitignore 文件,可以使用通配符和注释来定义忽略规则。以下是一些常见的用法示例:
下面是一个示例 .dockerignore 文件:
# 忽略所有的日志文件
*.log
# 忽略临时文件和目录
tmp/
*.tmp
# 忽略版本控制目录
.git/
.gitignore
# 忽略编译生成的文件
build/
# 忽略操作系统生成的文件
.DS_Store
Thumbs.db
在这个示例中,我们忽略了所有的 .log 文件、tmp 目录、.git 目录以及其他一些常见的临时文件和操作系统生成的文件,当我们构建 Docker 镜像时,这些文件和目录将不会被包含在构建上下文中,你可以理解成它们被 “隐藏” 了。