一文搞懂Dockerfile

Dockerfile官网

https://docs.docker.com/reference/dockerfile/

什么是Dockerfile?

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

当我们从docker镜像仓库中下载的镜像不能满足我们的需求时,我们可以通过以下两种方式对镜像进行更改:

  1. 从已经创建的容器中更新镜像,并且提交这个镜像
  2. 使用 Dockerfile 指令来创建一个新的镜像

镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。

那Dockerfile有什么作用呢?

  • 对于开发人员,可以为开发团队提供一个完全一致的开发环境
  • 对于测试人员,可以直接拿开发时所构建的镜像测试。
  • 对于运维人员,在部署时,可以实现快速部署、移值。

Dockerfile指令

FROM

FROM指令初始化一个新的构建阶段,并为后续指令设置 基础镜像。因此,有效的 Dockerfile 必须以FROM指令开头。镜像可以是任何有效的镜像。FROM指定一个基本镜像,类似docker pull下载镜像。

FROM可以在单个 Dockerfile 中多次出现,以创建多个镜像,或将一个构建阶段用作另一个构建阶段的依赖项。

示例:

复制代码
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

FROM java:8

LABEL

指定镜像的元数据,例如作者、联系方式、邮箱等信息

语法:

复制代码
LABEL <key>=<value> [<key>=<value>...]
LABEL author=huangSir

RUN

RUN指令制作镜像过程中需要执行的命令,通常系统配置,服务配置,下载软件,部署等等,不能出现阻塞当前终端的命令.

RUN它有两种形式:

复制代码
#shell脚本的形式:
RUN [OPTIONS] <command> ...

#Exec的形式:
RUN [OPTIONS] [ "<command>", ... ]

通常RUN指令种,shell脚本是最常用的:

复制代码
#第一种heredocs形式
RUN <<EOF
apt-get update
apt-get install -y curl
EOF
#第二种换行符的形式
RUN apt-get update \
    && apt-get install -y curl \
    && curl www.baidu.com

ADD

可以把指定文件、目录、以及远程 HTTPS 和 Git URL 获取文件的功能添加至镜像的指定目录中,如果是压缩包会自动进行解压

示例:

复制代码
#将本地文件添加至镜像中
ADD nginx.tar /app/tools/
ADD file1.txt file2.txt /usr/src/things/

#要从远程位置添加文件,您可以指定 URL 或 Git 仓库的地址作为源。例如:
ADD https://example.com/archive.zip /usr/src/things/
ADD [email protected]:user/repo.git /usr/src/things/

COPY

COPY功能类似ADD,但是不会解压压缩包,同时可以进行多阶段构建镜像,以助于减小镜像的大小

复制代码
#将本地文件添加至镜像中
COPY nginx.tar /app/tools/
COPY file1.txt file2.txt /usr/src/things/

#要从远程位置添加文件,您可以指定 URL 或 Git 仓库的地址作为源。例如:
COPY https://example.com/archive.zip /usr/src/things/
COPY [email protected]:user/repo.git /usr/src/things/

WORKDIR

指定容器的默认工作目录,也就是设置进入容器时默认所在的路径,例如:

复制代码
WORKDIR /path/to/workdir
RUN pwd

上面会输出/path/to/workdir,也相当于你进入容器时所在的目录为/path/to/workdir,并且当目录不存在时会自动创建它。

WORKDIR指令可以在 Dockerfile 中多次使用。如果提供了相对路径,则该路径将相对于前一条 WORKDIR指令的路径,例如:

复制代码
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

此时会输出/a/b/c

VOLUME

设置需要挂载的数据卷,创建镜像之后运行容器可以使用-v选项指定。

VOLUME的值可以是 JSON 数组,VOLUME ["/var/log/"],也可以是包含多个参数的纯字符串,例如VOLUME /var/logVOLUME /var/log /var/db

示例:

复制代码
VOLUME ["/var/log/","/data/mysql"]
VOLUME /var/log
VOLUME /var/log /var/db

EXPOSE

EXPOSE指令告知 Docker 容器在运行时监听指定的网络端口。您可以指定端口监听 TCP 还是 UDP,如果不指定协议,则默认为 TCP。

示例:

复制代码
EXPOSE 80/tcp
EXPOSE 80/udp

CMD

CMD指令设置从镜像运行容器时要执行的默认的命令,可以在docker run的时候进行替换:

示例:

复制代码
#例如Dockerfile如下:
FROM alpine
CMD ["echo", "Hello, Docker!"]

#运行容器时,可以通过以下方式覆盖默认的 CMD:
docker run my-image echo "Custom Message"

CMD可以使用 shell 或 exec 形式指定指令:

复制代码
#exec形式
CMD ["executable","param1","param2"]
CMD ["param1","param2"]

#shell形式
CMD command param1 param2

Dockerfile 中只能有一条CMD 指令。如果列出多条CMD指令,则只有最后一条指令生效。

ENTRYPOINT

ENTRYPOINT也用于指定容器的入口命令,无法被docker run替换,docker run指定的时候仅仅作为后续追加的命令,可以和CMD指令进行搭配使用。

ENTRYPOINT可以使用shell或exec形式指定指令

复制代码
#exec形式
ENTRYPOINT ["executable","param1","param2"]
ENTRYPOINT ["param1","param2"]

#shell形式
ENTRYPOINT command param1 param2

当 Dockerfile 中有多条ENTRYPOINT指令时,只有最后一条ENTRYPOINT指令才会有效。

ENV

创建容器/镜像全局变量,后续Dockerfile中使用该变量时可以${变量名}使用

示例:

复制代码
ENV FILE_PATH=/data/mysql
RUN mkdir -p ${FILE_PATH}

#会输出/data/mysql
RUN echo ${FILE_PATH}

ARG

ARG指令定义了一个变量,用户可以在构建时docker build使用--build-arg <varname>=<value> 标志的命令将该变量传递给构建器。

Dockerfile 可以包含一条或多ARG条指令。例如,以下是一个有效的 Dockerfile:

复制代码
FROM busybox
ARG user1
ARG buildno

也可以给ARG设置默认值:

复制代码
FROM busybox
ARG user1=someuser
ARG buildno=1

USER

USER 指令用于指定后续指令执行时所使用的用户。这在需要以非 root 用户运行容器时非常有用,可以提高容器的安全性,避免以 root 用户运行可能导致的安全风险。

示例:

复制代码
FROM busybox
RUN useradd www-www
USER www-www
RUN apt install curl

HEALTHCHECK

HEALTHCHECK指令告诉 Docker 如何测试容器是否仍在运行。这可以检测出诸如 Web 服务器陷入无限循环、无法处理新连接等情况,即使服务器进程仍在运行。

当容器指定了健康检查后,除了正常状态外,它还会拥有一个健康状态。此状态最初为starting。每当健康检查通过时,它都会变为healthy(无论它之前的状态如何)。在连续失败一定次数后,它将变为unhealthy。

示例:

复制代码
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
           CMD curl http://127.0.0.1:8080

#--interval 间隔多少秒执行CMD的内容
#--timeout 指定超时时间是多少
#--retries 允许重试几次
#CMD 执行的命令

总结

上述指令在生产过程中均已包含,当然还有几个指令没有说明,例如SHELL(可以使用RUN指令替换)、MAINTAINER(该指令已弃用,可以使用LABEL标签替换)ONBUILD、STOPSIGNAL这四个指令,感兴趣的同学可以自行查阅相关资料

Dockerfile相关命令说明

语法:

复制代码
docker build [选项] <上下文路径>

常用选项说明:

  • -t:指定镜像的名称和标签(tag)。格式为 <name>:<tag>,其中 <tag> 是可选的
  • -f:指定 Dockerfile 的路径。默认情况下,Docker 会在当前目录下查找名为 Dockerfile 的文件。
  • --build-arg:设置构建时的变量,用于传递给 ARG 指令。
  • --no-cache:禁用缓存,强制重新构建。
  • --rm:构建完成后删除中间容器,默认值为 true
  • --debug:debug测试Dockerfile

示例:

假设你的 Dockerfile 位于当前目录下,你可以使用以下命令构建镜像:

复制代码
docker build -t my-image .

如果 Dockerfile 不在当前目录下,可以使用 -f 指定路径:

复制代码
docker build -f /path/to/Dockerfile -t my-image /path/to/context

-f /path/to/Dockerfile:指定 Dockerfile 的路径。
/path/to/context:指定构建上下文的路径。

如果 Dockerfile 中使用了 ARG 指令,可以通过 --build-arg 参数在构建时传递值。

示例 Dockerfile

复制代码
FROM alpine
ARG MY_VAR=default_value
RUN echo "The value is $MY_VAR"

构建命令

复制代码
docker build --build-arg MY_VAR=custom_value -t my-image .

总结

一般来说,Dockerfile文件和文件中指定的其它文件,都会指定在同一个目录下,所以我们只需要记住下面这个命令即可

复制代码
#最后有个点,代表当前目录下的所有文件内容
docker build -t 镜像名:版本号 .

使用tomcat构建Java项目

这里使用zrlog的war包来代表生产环境中的实际项目,在这里感谢zrlog的提供方:https://gitee.com/94fzb/zrlog

下载war包:

复制代码
[root@master /data/docker/zrlog]# wget wget https://dl.zrlog.com/release/zrlog.war
--2025-04-13 14:59:30--  http://wget/
Resolving wget (wget)... failed: Temporary failure in name resolution.
wget: unable to resolve host address 'wget'
--2025-04-13 14:59:30--  https://dl.zrlog.com/release/zrlog.war
Resolving dl.zrlog.com (dl.zrlog.com)... 154.17.16.140
Connecting to dl.zrlog.com (dl.zrlog.com)|154.17.16.140|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10794045 (10M) [application/java-archive]
Saving to: 'zrlog.war'

zrlog.war                                           100%[==================================================================================================================>]  10.29M  4.63MB/s    in 2.2s

2025-04-13 14:59:33 (4.63 MB/s) - 'zrlog.war' saved [10794045/10794045]

FINISHED --2025-04-13 14:59:33--
Total wall clock time: 3.3s
Downloaded: 1 files, 10M in 2.2s (4.63 MB/s)

[root@master /data/docker/zrlog]# ll
total 10552
drwxr-xr-x 2 root root     4096 Apr 13 14:59 ./
drwxr-xr-x 5 root root     4096 Apr 13 14:59 ../
-rw-r--r-- 1 root root 10794045 Jul 10  2024 zrlog.war

编写Dockerfile:

复制代码
[root@master /data/docker/zrlog]# cat Dockerfile
FROM tomcat:9.0.87-jdk8-corretto
LABEL author=huangSir
LABEL version=1.0

ENV CODE_FILE="zrlog.war"
ENV SRC_PATH="/usr/local/tomcat/webapps/ROOT.war"
ENV WORK_DIR="/usr/local/tomcat"

COPY ${CODE_FILE} ${SRC_PATH}
WORKDIR ${WORK_DIR}

EXPOSE 8080
CMD ["catalina.sh","run"]
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
            CMD curl http://127.0.0.1:8080

构建镜像并查看

复制代码
#构建镜像
[root@master /data/docker/zrlog]# docker build -t zrlog_tomcat:8.0 .
[+] Building 0.2s (8/8) FINISHED                                                                                                                                                                docker:default
 ...
 => => naming to docker.io/library/zrlog_tomcat:8.0                           
 
#查看构建的镜像
[root@master /data/docker/zrlog]# docker images
REPOSITORY             TAG       IMAGE ID       CREATED              SIZE
zrlog_tomcat           8.0       be4206f2e7bf   About a minute ago   467MB

运行容器

复制代码
[root@master /data/docker/zrlog]# docker run -d -p 8080:8080 --name zrlog --restart always zrlog_tomcat:8.0
8cb866a61b46bb04bcf1a04a064388d6a8c985d84ea9a3a11478086ad89cf28c
[root@master /data/docker/zrlog]# docker ps -a
CONTAINER ID   IMAGE              COMMAND                  CREATED         STATUS         PORTS                                                    NAMES
8cb866a61b46   zrlog_tomcat:8.0   "catalina.sh run"        4 seconds ago   Up 4 seconds   0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp, 8443/tcp    zrlog

测试访问:
http://10.0.0.10:8080/

Dockerfile书写要求

合理编写Dockerfile可以减少构建出来的镜像的大小,避免构建Docker镜像时产生过多的层级。

Dockerfile生产环境书写要求:

1、尽量保证每个镜像功能单一,尽量避免多个服务运行在同一个镜像中

2、选择合适的基础镜像,不一定都要从头做

3、注释与说明,添加一定的注释和镜像属性信息

4、指定版本号

5、减少镜像的层数/步骤,尽可能合并RUN,ADD,COPY

6、记得减少镜像的大小,清理垃圾,记得清理缓存,临时文件,压缩包等等...

7、合理使用.dockeringnore,Dockerfile同一个目录,隐藏文件,构建的忽略的文件

.dockerignore使用

.dockerignore是一个隐藏文件,在Dockerfile文件同级目录中

示例:

复制代码
[root@master /data/docker/zrlog]# ll
total 10556
drwxr-xr-x 2 root root     4096 Apr 13 15:33 ./
drwxr-xr-x 5 root root     4096 Apr 13 14:59 ../
-rw-r--r-- 1 root root        0 Apr 13 15:33 .dockerignore
-rw-r--r-- 1 root root      370 Apr 13 15:25 Dockerfile
-rw-r--r-- 1 root root 10794045 Jul 10  2024 zrlog.war

.dockerignore 文件是 Docker 构建过程中使用的一个配置文件,类似于 Git 中的 .gitignore 文件。它的作用是告诉 Docker 在构建镜像时,应该忽略哪些文件和目录,从而避免将不必要的文件复制到镜像中,减少镜像大小并提高构建效率。

示例:

复制代码
# 忽略所有以 . 开头的文件
.*

# 但不忽略 .dockerignore 文件本身
!.dockerignore

# 忽略临时文件
*.tmp
*.swp

# 忽略日志文件
*.log

# 忽略测试目录
tests/

# 忽略数据目录下的敏感文件
data/sensitive_data.txt

# 忽略虚拟环境
venv/

# 忽略构建文件
build/
dist/

# 忽略特定文件
requirements.txt

# 准许Dockerfile文件
!Dockerfile

# 准许entrypoint.sh脚本
!entrypoint.sh

说明:

*:表示排除所有

!:准许指定的文件传输到docker镜像中

Dockerfile多阶段构建

Docker 的多阶段构建(Multi-Stage Builds)是一种强大的功能,允许你在同一个 Dockerfile 中定义多个构建阶段,每个阶段可以基于不同的基础镜像。最终,你可以从这些阶段中选择需要的文件和目录,构建出一个更小、更安全的最终镜像。多阶段构建特别适用于需要编译源代码或安装大量依赖的应用程序。

多阶段构建的优势

  • 减小镜像大小:只将必要的文件和依赖复制到最终镜像中,避免包含编译工具和中间文件。
  • 提高安全性:避免将编译工具和源代码暴露在最终镜像中。
  • 简化构建过程:在一个 Dockerfile 中完成所有构建步骤,无需多个 Dockerfile 或复杂的构建脚本。

多阶段构建的基本结构

多阶段构建的 Dockerfile 通常包含多个 FROM 指令,每个 FROM 指令定义一个新的阶段。你可以为每个阶段指定一个名称(通过 AS 关键字),并在后续阶段中引用这些名称。

示例:

复制代码
# 第一阶段:构建阶段
FROM maven:3.8.1-jdk-11 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package

# 第二阶段:运行阶段
FROM openjdk:11-jre-slim
COPY --from=builder /app/target/my-java-app.jar /app.jar
CMD ["java", "-jar", "/app.jar"]

说明:

上述第一阶段构建时,使用maven打包成一个jar包,然后在第二阶段构建时,会将第一阶段打包的jar包copy到第二阶段中进行运行