Dockerfile详解

Dockerfile详解

九月 08, 2025 次阅读

什么是 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 定义的镜像可以在任何支持 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/amd64linux/arm64 等)。这在构建多架构镜像时非常有用。
  • <image>:必需参数,指定基础镜像的名称,可以是官方镜像(如 ubuntualpine)或自定义镜像。
  • [:<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"

该指令为镜像添加了两个标签,分别是 companyapp,用于描述镜像的所属公司和应用名称。

运行如下:

┌─[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"
          },
......

我们可以看到,在该镜像的元数据中成功添加了 companyapp 标签。

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 -pdocker run -P 命令结合使用,以实现端口映射。

语法格式:

EXPOSE <port> [<port>/<protocol>...]
  • <port>:必需参数,指定要暴露的端口号,可以是单个端口或多个端口。
  • <protocol>:可选参数,指定端口使用的协议,默认为 TCP。可以是 tcpudp

示例:

继续使用之前的 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 页面;

index

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 指令定义的卷在容器运行时会被挂载到宿主机的文件系统中,从而实现数据的持久化和共享。与 COPYADD 指令不同,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

该示例中,我们创建了两个用户 nginxmysql,并在不同的用户身份下执行了 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 可以定期检查容器内应用程序的状态。通过健康检查,可以确保容器内的服务正常运行,并在出现问题时采取相应的措施,例如重启容器或发送警报。健康检查命令可以是任何返回状态码的命令,通常使用 curlwget 来检查 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 指令,例如 RUNCOPYADD 等。

我们构建两个镜像,其中一个作为基础镜像,另一个作为子镜像来演示 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 时,既要保证镜像构建的高效性,也要关注镜像的体积、可维护性和可移植性。以下几点是常见且有效的优化策略:

  1. 使用 .dockerignore 文件

    • 忽略构建过程中无关的文件(如 .git、日志、测试文件等),减少构建上下文的大小,加快镜像构建速度。
  2. 采用多阶段构建(Multi-stage build)

    • 在前期阶段完成编译、打包,在最终阶段仅保留运行所需文件。
    • 避免将编译工具、构建依赖带入最终镜像,从而得到精简、安全的运行镜像。
  3. 合理利用缓存机制

    • 将变化频率低的指令放在前面(如依赖安装),变化频率高的放在后面(如代码拷贝)。
    • 这样可以最大化地复用缓存层,减少重复构建时间。
  4. 选择合适的基础镜像

    • 优先使用官方镜像,保证安全性与长期维护性。
    • 根据场景选择轻量化镜像(如 alpinedebian:slimbusybox),避免使用过大的基础镜像(如完整的 ubuntu),降低最终镜像体积。
  5. 减少镜像层数

    • 合并多个 RUNCOPYADD 指令,避免产生过多层。
    • 示例:RUN apt-get update && apt-get install -y curl vim && rm -rf /var/lib/apt/lists/*
  6. 保证镜像用途单一

    • 每个镜像应专注于一个服务或功能,避免“万能镜像”带来复杂性与维护难度。
  7. 固定外部依赖版本

    • 下载外部数据或依赖时,指定版本和固定地址,确保构建过程可重复,避免因外部源变动而导致构建失败或结果不一致。
  8. 只安装必要的软件包

    • 避免安装无关的调试工具或多余软件,减少镜像体积与潜在安全风险。

优秀的 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 中,ENTRYPOINTCMD 都用于指定容器启动时要执行的命令,但它们有一些关键区别和搭配方式。


1. 基本作用

  • ENTRYPOINT

    • 用来定义容器启动时的固定执行命令
    • 一般用于指定容器的主要功能。
    • 用户在 docker run 时提供的命令会作为 参数 传递给 ENTRYPOINT
  • CMD

    • 提供默认命令或默认参数。
    • 如果用户在 docker run 时指定了命令,会覆盖 CMD
    • 单独存在时也能定义入口,但更常见的用途是给 ENTRYPOINT 提供参数。

2. 覆盖规则

  • Dockerfile 内部:新的 ENTRYPOINTCMD 会覆盖前面定义的同类指令。

  • 容器运行时

    • 如果 Dockerfile 使用了 CMD,用户 docker run <image> <command> 会直接覆盖 CMD
    • 如果 Dockerfile 使用了 ENTRYPOINT,用户输入会变成参数,除非用 --entrypoint 选项替换掉。

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 镜像时,这些文件和目录将不会被包含在构建上下文中,你可以理解成它们被 “隐藏” 了。