Dockerfile 是 Docker 镜像构建的核心,它通过一系列指令自动化地定义了镜像的构建过程。下面我们将详细介绍 Dockerfile 的制作流程,并通过案例展示其应用。
在讨论 Dockerfile 的制作流程之前,我们先来探讨为什么要使用 Dockerfile 进行自动构建。
Dockerfile 是构建 Docker 镜像的核心脚本,它包含了一系列的指令,这些指令定义了镜像的构建过程。每个指令都会创建一个新的层,这些层最终组合成一个完整的镜像。Dockerfile 的设计哲学与 Makefile 类似,都用于自动化构建过程,但 Dockerfile 更专注于容器镜像的构建。
一个 Dockerfile 通常包含以下四个部分:
手动构建镜像就像是直接烹饪一道菜,而 Dockerfile 则像是这道菜的食谱。使用 Dockerfile,你只需按照食谱上的步骤操作,就可以复现相同的菜式。
docker build
构建镜像:运行 docker build
命令,Docker 会根据 Dockerfile 中的指令构建镜像。FROM
指令用于指定基础镜像,是 Dockerfile 中的第一条指令。
有两种格式
FROM<image> 指定基础image为该image的最后修改的版本。
或者:
FROM<image>:<tag> 指定基础image为该image的一个tag版本
示例:
FROM ubuntu:18.04
1. Shell 格式:
RUN <command> (the command is run in a shell - `/bin/sh -c`)
这种格式在 shell 环境中执行命令,允许使用 shell 的特性,如变量替换、管道、通配符等。
2. Exec 格式:
RUN ["executable", "param1", "param2", ... ] (exec form)
这种格式直接执行命令,不通过 shell。这有助于避免 shell 带来的潜在安全风险,并提供更清晰的执行环境。
当 RUN
指令中的命令较长,为了提高可读性和便于维护,可以使用反斜杠 \
进行命令换行:
RUN apt-get update \ && apt-get install -y \ curl \ vim
在 Dockerfile 中,ADD
和 COPY
是两个常用的指令,用于将文件从构建上下文(通常是 Dockerfile 所在的目录)复制到构建中的容器镜像中。它们在功能上相似,但也存在一些差异。
格式:
ADD <src> <dest>
说明:
<src> 可以是 Dockerfile 所在目录的相对路径,也可以是一个 URL,或者是一个 tar 文件(在这种情况下,它将被自动解压)。
<dest> 是容器中的绝对路径,或者是相对于 WORKDIR 指令设置的路径。
特点:
ADD 会保留文件的权限,但所有文件和文件夹的权限会被设置为 0755,uid 和 gid 被设置为 0。
如果 <src> 是一个目录,那么只有目录内的内容会被复制,不包括目录本身。
如果 <src> 是一个可识别的压缩格式,Docker 会自动解压缩它。
如果 <src> 是一个文件,并且 <dest> 以斜杠 / 结尾,那么 <src> 将被拷贝到 <dest> 目录下。
如果 <src> 是一个文件,并且 <dest> 没有以斜杠 / 结尾,那么 <src> 的内容将被写入 <dest>。
格式:
COPY <src> <dest>
说明:
COPY 只能访问 Dockerfile 所在目录(构建上下文)中的文件,不能访问 URL 或 tar 文件。
<dest> 的工作方式与 ADD 相同
。特点:
COPY 不会自动解压缩 tar 文件,它仅仅是复制文件或目录。
COPY 在权限和所有权方面比 ADD 更透明,它保留了文件原有的权限和所有权。
ADD local-file /dest-path
ADD http://example.com/remote-file /dest-path
COPY local-file /dest-path
ADD
可以下载文件,因此如果使用 URL 作为 <src>
,需要注意安全性和信任问题。COPY
在大多数情况下更推荐使用,因为它的行为更可预测,更透明。ADD
指令。ENV
指令在 Dockerfile 中用于设置环境变量,这些环境变量在后续的 RUN
、CMD
、ENTRYPOINT
、COPY
和 ADD
指令中都可用,并且会持续存在于镜像中,直到容器的生命周期结束。
ENV
指令有两种格式:
ENV <key1>=<value1> <key2>=<value2> ...
FROM ubuntuENV APP_HOME /appENV PATH=$APP_HOME:$PATH
在这个例子中,我们设置了两个环境变量:
APP_HOME
被设置为 /app
。PATH
被修改为在原有的 PATH
基础上添加了 APP_HOME
的值。ENV
指令修改。ENV
指令中设置敏感信息,如密码或密钥。VOLUME
指令在 Dockerfile 中用于定义容器中的一个挂载点,它使得该目录可以作为数据卷,实现数据的持久化存储。在 Docker 中,数据卷是持久化存储和共享数据的一种机制,它们可以独立于容器的生命周期,即使容器被删除,数据卷中的数据也不会丢失。
VOLUME ["<mountpoint>"]
<mountpoint>
是容器内部的绝对路径,它指定了挂载点的位置。FROM baseVOLUME ["/tmp/data"]
在这个例子中,/tmp/data
目录被定义为数据卷,它允许容器在运行时将该目录挂载到宿主机或其他容器的文件系统上。
当使用 docker run
命令启动容器时,可以通过 -v
或 --volume
选项来挂载数据卷:
docker run -d --name my_container -v /tmp/data my_image
这个命令将宿主机上的 /tmp/data
目录挂载到容器内部的 /tmp/data
目录。
Docker 允许通过 --volumes-from
选项在容器之间共享数据卷:
docker run -t -i -rm --volumes-from my_container -name another_container my_image bash
在这个例子中,another_container 可以访问 my_container 的 /tmp/data 数据卷。
EXPOSE
指令在 Dockerfile 中用于声明容器在运行时需要暴露的端口号,这些端口在容器内部的应用程序中用于监听。EXPOSE
指令不会实际上将端口映射到宿主机上,而是作为一个声明,告知用户哪些端口在运行容器时应该被映射。
EXPOSE <port> [<port>...]
这里的 <port>
可以是一个具体的端口号,也可以是一个端口范围。
尽管 EXPOSE
指令在 Dockerfile 中声明了需要暴露的端口,但实际的端口映射是在运行容器时通过 docker run
命令的 -p
或 --publish
选项来完成的。
docker run -p 80 image
这将容器的 80 端口映射到宿主机的一个随机端口上。
docker run -p 8080:80 image
这将容器的 80 端口映射到宿主机的 8080 端口上。
docker run -p 8080:80 -p 8443:443 image
这将容器的 80 端口映射到宿主机的 8080 端口,同时将容器的 443 端口映射到宿主机的 8443 端口。
docker ps
可以查看容器的端口映射情况,或者使用 docker port <container_id_or_name> <port>
来查看特定端口在宿主机上的映射。 CMD
是 Dockerfile 中的一个指令,用于指定容器启动时默认执行的命令。这个指令非常重要,因为它定义了容器的预期行为或进程。以下是 CMD
指令的三种格式及其使用方式:
CMD ["executable", "param1", "param2"]
这种格式使用 JSON 数组直接指定可执行文件及其参数。这是推荐的方式,因为它清晰、易于调试,并且可以确保可执行文件及其参数被正确地传递给 shell。
CMD command param1 param2
这种格式在 shell (/bin/sh -c
) 中执行命令。这适用于需要交互式 shell 或执行 shell 脚本的情况。
CMD ["param1", "param2"]
当 Dockerfile 中指定了 ENTRYPOINT
指令时,CMD
可以用于提供 ENTRYPOINT
的默认参数。在这种情况下,CMD
必须使用 JSON 数组格式。
CMD ["sh", "-c", "echo Hello World"]
CMD echo Hello World
ENTRYPOINT ["/usr/bin/my_app"]CMD ["--arg1", "value1"]
在这个例子中,容器启动时将执行 /usr/bin/my_app --arg1 value1
。
CMD
命令。如果有多条,只有最后一条会被执行。CMD
指定的命令。CMD
用于给 ENTRYPOINT
提供参数,它必须使用 JSON 数组格式。 ENTRYPOINT
指令在 Dockerfile 中用于定义容器启动时执行的命令。它对于设置容器的行为非常关键,尤其是当你希望无论传递什么参数,容器都能以一种特定的方式运行时。
ENTRYPOINT ["executable", "param1", "param2"]
这种格式使用 JSON 数组直接指定可执行文件及其参数。
ENTRYPOINT command param1 param2
这种格式在 shell 中执行命令。注意,这种格式在 Dockerfile 中不太常用,因为它可能受到 shell 环境的影响,导致跨平台问题。
ENTRYPOINT
独立使用时,它指定的命令将在容器启动时执行,并且不会被 docker run
提供的任何参数覆盖。FROM ubuntuENTRYPOINT ["top", "-b"]
ENTRYPOINT
与 CMD
配合使用时,CMD
指定的参数将传递给 ENTRYPOINT
指定的命令。在这种情况下,CMD
不是一个完整的命令,而是参数。FROM ubuntuCMD ["-l"]ENTRYPOINT ["/usr/bin/ls"]
在这个例子中,容器启动时将执行 /usr/bin/ls -l
。
ENTRYPOINT
指令。如果有多个,只有最后一个会生效。ENTRYPOINT
与 CMD
配合使用时,CMD
提供的参数将作为 ENTRYPOINT
命令的参数。ENTRYPOINT
之后还使用了 CMD
,并且 CMD
是一个完整的命令,那么 ENTRYPOINT
将被覆盖。 在 Dockerfile 中使用 USER
指令可以指定运行容器时的用户。默认情况下,容器以 root
用户运行,但出于安全考虑,如果服务不需要管理员权限,可以通过 USER
指令指定一个非 root 用户来运行容器。
USER <用户名或UID>
或者
USER <用户名>:<用户组或GID>
USER daemon
RUN groupadd -r postgres && useradd -r -g postgres postgresUSER postgres
ENTRYPOINT
结合使用: 如果服务的可执行文件接受用户参数,可以直接在 ENTRYPOINT
中指定:ENTRYPOINT ["memcached", "-u", "daemon"]
或者,如果服务的可执行文件不接受用户参数,可以在 USER
指令中指定:
ENTRYPOINT ["memcached"]USER daemon
root
用户运行,确保该用户具有执行所需操作的权限。USER
指令应该在需要以特定用户身份执行的命令之前。例如,任何 RUN
指令,如果需要特定用户权限,都应该在 USER
指令之后。useradd
命令在镜像构建过程中创建用户。 WORKDIR
指令在 Dockerfile 中用于为容器设置工作目录,即容器内部的当前目录。这个目录对于后续的 RUN
、CMD
、ENTRYPOINT
、COPY
和 ADD
指令是生效的。如果 WORKDIR
指定的目录不存在,Docker 会自动创建所有需要的中间目录。
WORKDIR /path/to/workdir
/path/to/workdir
是容器内部的绝对路径,或者是相对于之前 WORKDIR
指令的相对路径。# 设置工作目录为 /appWORKDIR /app
# 等同于 WORKDIR /appRUN mkdir app
# 将工作目录切换到上一步创建的 app 目录WORKDIR app
# 此时执行 vim a.txt,是在 /app/app 目录下执行RUN vim a.txt
WORKDIR
可以接受相对路径,它相对于上一个 WORKDIR
指定的路径。WORKDIR
指令可以叠加路径,Docker 会创建所有中间目录。WORKDIR
也可以使用环境变量,例如 WORKDIR $USER/home
。 ONBUILD
是 Dockerfile 中的一个特殊指令,它用于在创建子镜像时自动执行特定的命令。这些命令在当前镜像构建过程中不会执行,而是在有人使用这个镜像作为基础镜像创建新镜像时触发。
ONBUILD <Dockerfile 指令>
这里的 <Dockerfile 指令>
可以是任何有效的 Dockerfile 指令,如 COPY
、RUN
、ADD
等。
HEALTHCHECK
是 Dockerfile 中的一个指令,用于指定如何对容器进行健康检查,这可以帮助确定容器是否仍在正常运行并且准备好接收流量。如果没有健康检查,容器管理工具(如 Docker 或 Kubernetes)可能很难知道一个容器是否已经失败或者无响应。
HEALTHCHECK [OPTIONS] CMD command (容器必须返回的状态码)HEALTHCHECK [OPTIONS] NONE
--interval=<duration>
:两次健康检查之间的时间间隔。--timeout=<duration>
:健康检查命令的超时时间。--start-period=<duration>
:在容器启动后,多久开始健康检查。--retries=<num-retries>
:健康检查失败后,容器重启前尝试的次数。以下是 HEALTHCHECK
指令的一个示例,它使用 curl
命令检查容器上的服务是否健康:
HEALTHCHECK--interval=5m--timeout=3s--start-period=1m--retries=3\ CMD curl -f http://localhost:80 || exit 1
在这个例子中:
--interval=5m
:每 5 分钟执行一次健康检查。--timeout=3s
:如果健康检查命令在 3 秒内没有返回,它将被视为失败。--start-period=1m
:容器启动 1 分钟后开始健康检查。--retries=3
:如果健康检查连续失败 3 次,Docker 将认为容器不健康,并可能采取行动,如重启容器。0
(成功)或 1
(失败)。CMD
后面跟的命令必须在容器内部运行,并且能够检测容器的健康状态。下面是一个使用上述指令的 Dockerfile 示例,构建一个运行 Nginx 服务的镜像:
# 使用官方的 Ubuntu 基础镜像FROM ubuntu:18.04
# 设置环境变量,指定时区ENV TZ=UTC \ DEBIAN_FRONTEND=noninteractive
# 更新包索引并安装 NginxRUN apt-get update && apt-get install -y nginx
# 将本地文件复制到容器中的 /app 目录COPY . /app
# 设置工作目录为 /appWORKDIR /app
# 监听的端口EXPOSE 80
# 设置容器启动时执行的命令CMD ["nginx", "-g", "daemon off;"]
# 设置健康检查HEALTHCHECK --retries=3 CMD [ "curl", "-f", "http://localhost" ]
构建镜像
docker build -t dockerfile-sre-nginx -f demo_2.dockerfile .[+] Building 69.6s (9/9) FINISHED docker:default => [internal] load build definition from demo_2.dockerfile 0.0s => => transferring dockerfile: 514B 0.0s => [internal] load metadata for docker.io/library/ubuntu:18.04 5.5s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [1/4] FROM docker.io/library/ubuntu:18.04@sha256:152dc042452c496007f07ca9127571cb9 8.3s => => resolve docker.io/library/ubuntu:18.04@sha256:152dc042452c496007f07ca9127571cb9 0.0s => => sha256:7c457f213c7634afb95a0fb2410a74b7b5bc0ba527033362c240c7 25.69MB / 25.69MB 6.4s => => sha256:152dc042452c496007f07ca9127571cb9c29697f42acbfad72324b2b 1.33kB / 1.33kB 0.0s => => sha256:dca176c9663a7ba4c1f0e710986f5a25e672842963d95b960191e2d9f718 424B / 424B 0.0s => => sha256:f9a80a55f492e823bf5d51f1bd5f87ea3eed1cb31788686aa99a2fb6 2.30kB / 2.30kB 0.0s => => extracting sha256:7c457f213c7634afb95a0fb2410a74b7b5bc0ba527033362c240c7a11bef4 1.7s => [internal] load build context 2.9s => => transferring context: 187.38MB 2.9s => [2/4] RUN apt-get update && apt-get install -y nginx 54.4s => [3/4] COPY . /app 1.0s => [4/4] WORKDIR /app 0.0s => exporting to image 0.3s => => exporting layers 0.3s => => writing image sha256:d72611e47ccde8d858882cdc2a23bf166c8a96fcdf2973e273495e5ca6 0.0s => => naming to docker.io/library/dockerfile-sre-nginx
启动容器
#构建好的镜像docker image lsREPOSITORY TAG IMAGE ID CREATED SIZEdockerfile-sre-nginx latest d72611e47ccd 8 minutes ago 356MB#启动容器docker run -it -d dockerfile-sre-nginx3a1bdfa88872ee2797181710b16189ec087ab2e67e13762c5eeacfa4a7e0163e
使用这个 Dockerfile,我们可以通过 Docker 构建一个镜像,该镜像启动后会运行 Nginx 服务,并且可以通过健康检查来验证服务是否正常运行。
在本文中,我们探讨了 Dockerfile 的重要性以及如何有效利用它来自动化 Docker 镜像的构建过程。Dockerfile 提供了一种声明式的方法来定义镜像内容,使得镜像的构建变得简洁、高效且易于维护。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。