Dockerfile 详解
🏠 回到主页
🐳 Docker 可以通过读取 Dockerfile 中的指令自动构建镜像. Dockerfile是一个文本文档, 它包含用户可以在命令行上调用的所有命令来组装一个镜像
🐳 格式
# 注释
INSTRUCTION arguments
Docker将以#
开头的行视为注释
Dockerfile 的指令不区分大小写
. 但是, 按照惯例, 应该使用大写
的, 目的是方便的将它们与参数区分开来
Docker在Dockerfile中按顺序运行指令. Dockerfile必须以FROM
指令开始. FROM
指令指定要构建的父映像. FROM
前面只能有一个或多个ARG
指令, 这些指令声明Dockerfile中FROM
行中使用的参数
🐳 Dockerfile 构建指令
🔘 FROM
FROM指令是Dockerfile的第一个指令,用于指定基础镜像。基础镜像是指Docker容器的根镜像,它包含了操作系统和一些常用的软件。在构建新的Docker容器时,我们可以基于一个已有的基础镜像来构建
使用FROM指令的语法如下:
FROM <image>[:<tag>] [AS <name>]
其中,image是基础镜像的名称,tag是基础镜像的版本号,AS name是可选的,用于给FROM指令指定一个别名
例如:
FROM ubuntu:16.04
该指令会基于Ubuntu 16.04版本的基础镜像来构建新的Docker容器
🔘 MAINTAINER
MAINTAINER指令用于指定维护者的信息,包括姓名和电子邮件地址
使用MAINTAINER指令的语法如下:
MAINTAINER <name> [<email>]
例如:
MAINTAINER John Doe <johndoe@example.com>
🔘 ARG
用于定义构建参数,这些参数可以在构建镜像时传递给 Docker 构建命令
ARG
的语法为:
ARG <name>[=<default value>]
其中 <name>
是参数名,<default value>
是可选的默认值
在 Dockerfile 中可以使用 ${<name>}
的形式来引用参数
例如,假设我们有以下 Dockerfile:
ARG NODE_VERSION=14
FROM node:${NODE_VERSION}
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
在构建镜像时,可以通过 --build-arg
参数来传递值,例如:
docker build --build-arg NODE_VERSION=16 -t my-node-app .
这会使用 Node.js 16.x 版本构建镜像
如果不传递 --build-arg
参数,则使用默认值,即 Node.js 14.x 版本构建镜像
注意:在 Dockerfile 中定义的 ARG
变量只在构建过程中存在,不会在容器运行时存在
🔘 ENV
ENV指令用于设置环境变量。在Docker容器中,可以使用ENV指令设置一些常用的环境变量,如PATH、HOME等
使用ENV指令的语法如下:
ENV <key> <value>
例如:
ENV LANG en_US.UTF-8
该指令会将Docker容器的默认语言设置为en_US.UTF-8
🔘 LABEL
用于向镜像添加元数据(metadata),元数据是一些描述镜像的键值对,可以包含版本、描述、维护者、构建日期等等信息。这些元数据可以帮助用户更好地管理和使用镜像
LABEL
指令的语法为:
LABEL key=value
其中,key
为元数据的键,value
为元数据的值。可以在 Dockerfile 中使用多个 LABEL
指令来添加多个元数据。
以下是一个使用 LABEL
指令的 Dockerfile 示例:
FROM ubuntu:latest
LABEL maintainer="John Doe <johndoe@example.com>"
LABEL version="1.0"
LABEL description="This is a sample Docker image"
上述 Dockerfile 定义了三个 LABEL
指令,分别添加了维护者、版本和描述信息。这些元数据可以使用 docker image inspect
命令查看,例如:
$ docker image inspect ubuntu:latest -f '{{json .Config.Labels}}' | jq
{
"maintainer": "John Doe <johndoe@example.com>",
"version": "1.0",
"description": "This is a sample Docker image"
}
使用 LABEL
指令的好处是,可以在镜像中添加一些有用的元数据,方便用户更好地管理和使用镜像。例如,可以使用 maintainer
元数据来指定维护者的联系方式,方便用户在使用镜像时获得支持和反馈
🔘 USER
USER指令用于设置Docker容器中执行命令的用户。在Docker容器中,可以使用USER指令将执行命令的用户从root用户切换到其他用户。
使用USER指令的语法如下:
USER <user>[:<group>]
例如:
该指令会将Docker容器中执行命令的用户切换到nginx用户
🔘 ADD
ADD指令用于将本地文件或目录复制到Docker容器中。在构建Docker镜像时,可以使用ADD指令将应用程序的代码和配置文件复制到容器中
使用ADD指令的语法如下:
ADD <src> <dest>
其中,src是本地文件或目录的路径,dest是Docker容器中的目标路径
例如:
ADD app /app
该指令会将本地的app目录复制到Docker容器的/app目录中
🔘 WORKDIR
WORKDIR指令用于设置Docker容器的工作目录。在Docker容器中,可以使用WORKDIR指令设置一个工作目录,以便在执行命令时不必每次都指定完整的路径。
使用WORKDIR指令的语法如下:
WORKDIR <path>
例如:
WORKDIR /app
该指令会将Docker容器的工作目录设置为/app
🔘 COPY
COPY指令与ADD指令类似,用于将本地文件或目录复制到Docker容器中。与ADD指令不同的是,COPY指令不会自动解压缩压缩文件,并且不支持从远程URL复制文件
使用COPY指令的语法如下:
COPY <src> <dest>
例如:
COPY app /app
该指令会将本地的app目录复制到Docker容器的/app目录中
🔘 RUN
RUN指令用于在Docker容器中执行命令或者脚本。在构建Docker镜像时,可以使用RUN指令执行一系列的命令来安装软件包、配置环境变量等操作
使用RUN指令的语法如下:
RUN <command>
例如:
RUN apt-get update && apt-get install -y nginx
该指令会在Docker容器中执行apt-get update和apt-get install -y nginx两个命令,以安装Nginx
🔘 ONBUILD
用于定义一个在当前镜像被用作其他镜像的基础镜像时需要执行的操作。也就是说,ONBUILD
指令是一种在构建一个镜像时预先为将来的使用者定义一些构建操作的方法
ONBUILD
指令的语法为:
ONBUILD [INSTRUCTION]
其中,INSTRUCTION
可以是任何有效的 Dockerfile 指令,例如 ADD
、RUN
、COPY
等。
当一个镜像被用作其他镜像的基础镜像时,Docker 将按照顺序执行该镜像中所有 ONBUILD
指令。这些操作会在构建子镜像时执行。也就是说,ONBUILD
指令定义了一些在子镜像构建时需要执行的操作。
例如,以下是一个使用 ONBUILD
指令的 Dockerfile 示例:
FROM ubuntu:latest
ONBUILD RUN echo "This is a build trigger message"
上面的 Dockerfile 中定义了一个 ONBUILD
指令,该指令会在当前镜像被用作其他镜像的基础镜像时执行。也就是说,当其他镜像从当前镜像构建时,Docker 将执行 echo "This is a build trigger message"
这个命令
在实际使用中,ONBUILD
指令通常用于定义一些通用的构建操作,例如安装依赖包、编译代码等。这样,其他用户在创建镜像时就不需要重复执行这些操作了
需要注意的是,ONBUILD
指令只有在当前镜像被用作其他镜像的基础镜像时才会执行。如果当前镜像不被用作其他镜像的基础镜像,则 ONBUILD
指令不会执行
🔘 VOLUME
VOLUME指令用于设置Docker容器的数据卷。数据卷是指Docker容器中的一个特殊目录,它可以被共享和重用,可以在容器之间传递数据
使用VOLUME指令的语法如下:
VOLUME <path> [<path>...]
例如:
VOLUME /data
该指令会在Docker容器中创建一个名为/data的数据卷
🔘 STOPSIGNAL
用于设置容器停止时发送给 Docker 守护进程的信号。该指令允许用户定义一个自定义的信号,以便在容器停止时进行清理工作或优雅地关闭服务
STOPSIGNAL
指令的语法为:
STOPSIGNAL signal
其中,signal
可以是一个数字或一个信号名称,例如 SIGTERM
、SIGINT
等
默认情况下,当容器停止时,Docker 会发送 SIGTERM
信号给容器内的主进程,如果主进程没有在一定时间内停止运行,Docker 会强制发送 SIGKILL
信号来强制终止容器。使用 STOPSIGNAL
指令可以将默认的停止信号改为其他信号
以下是一个使用 STOPSIGNAL
指令的 Dockerfile 示例:
FROM ubuntu:latest
STOPSIGNAL SIGINT
上述 Dockerfile 定义了一个 STOPSIGNAL
指令,以便在容器停止时发送 SIGINT
信号。这意味着,当用户停止容器时,Docker 守护进程会发送 SIGINT
信号给容器内的主进程,以便进行清理工作或优雅地关闭服务
需要注意的是,STOPSIGNAL
指令只能在 Dockerfile 中使用一次,且必须位于 Dockerfile 的末尾。此外,STOPSIGNAL
指令只适用于 Linux 系统
在 Linux 系统中,可以使用 kill -l
命令列出所有支持的信号类型。以下是常见的一些信号类型及其含义:
SIGHUP
:终端挂起或控制进程终止信号SIGINT
:中断进程信号SIGQUIT
:退出进程信号SIGKILL
:强制终止进程信号SIGTERM
:终止进程信号SIGUSR1
:用户自定义信号 1SIGUSR2
:用户自定义信号 2SIGPIPE
:管道破裂信号SIGALRM
:定时器信号SIGSEGV
:段错误信号SIGCHLD
:子进程状态改变信号SIGCONT
:继续执行进程信号SIGSTOP
:停止进程信号SIGTSTP
:终端停止进程信号SIGTTIN
:后台进程从终端读取数据信号SIGTTOU
:后台进程向终端输出数据信号
需要注意的是,不是所有的信号类型都可以作为 STOPSIGNAL
指令的参数。在 Docker 官方文档中,只有 SIGHUP
、SIGINT
、SIGQUIT
、SIGABRT
、SIGKILL
和 SIGTERM
可以被用作 STOPSIGNAL
指令的参数
🔘 EXPOSE
EXPOSE指令用于设置Docker容器对外暴露的端口号。当Docker容器启动时,可以通过指定的端口号来访问容器中运行的应用程序
使用EXPOSE指令的语法如下:
EXPOSE <port> [<port>...]
例如:
EXPOSE 80
该指令会将Docker容器的80端口对外暴露,以便外部访问
🔘 HEALTHCHECK
用于定义镜像的健康检查方式,以便在容器运行时监控容器的健康状况
HEALTHCHECK
的语法为:
HEALTHCHECK [OPTIONS] CMD command
其中 OPTIONS
可选,可以指定一些参数,例如:
--interval=<duration>
:设置检查间隔,默认为 30 秒--timeout=<duration>
:设置检查超时时间,默认为 30 秒--start-period=<duration>
:容器启动后,等待指定时间再进行第一次检查,默认为 0 秒
CMD
指定了要执行的检查命令
例如,以下是一个使用 HEALTHCHECK
的 Dockerfile 示例:
FROM node:12
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD curl --fail http://localhost:3000/ || exit 1
CMD ["npm", "start"]
上面的 Dockerfile 中定义了一个健康检查方式,每 30 秒执行一次 curl
命令,检查应用程序是否能够响应 HTTP 请求。如果检查失败,则容器的健康状况被标记为 unhealthy
在运行容器时,可以使用 docker ps
命令查看容器的健康状况:
$ docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Health}}"
NAMES STATUS HEALTH
my-node-app Up 5 seconds healthy
上面的输出表明,容器 my-node-app
的健康状况为 healthy
。如果健康状况为 unhealthy
,则说明容器的应用程序出现了问题,需要进一步诊断和修复
🔘 SHELL
用于在 Dockerfile 中设置默认的 shell 环境。在 Dockerfile 中执行的所有命令都将使用该 shell
SHELL
指令的语法为:
SHELL ["executable", "parameters"]
其中,executable
指定要使用的 shell 可执行文件,parameters
指定要传递给可执行文件的参数。
例如,以下是一个使用 SHELL
指令的 Dockerfile 示例:
FROM ubuntu:latest
SHELL ["/bin/bash", "-c"]
RUN echo "Hello, World!"
上面的 Dockerfile 中使用 SHELL
指令将默认的 shell 环境设置为 /bin/bash
,并将 -c
参数传递给该 shell。因此,在执行 RUN
命令时,Docker 将使用 /bin/bash -c
来解释命令。
在实际使用中,可以根据需要设置不同的 shell 环境,例如:
SHELL ["/bin/sh", "-c"]
SHELL ["/bin/bash", "-c"]
SHELL ["/usr/bin/env", "bash"]
注意,SHELL
指令只能在 Dockerfile 中定义一次,且必须作为 Dockerfile 的第一条指令。此外,SHELL
指令的参数必须是一个数组,不能是字符串
🔘 CMD
CMD指令用于设置Docker容器启动时执行的默认命令或者脚本。当Docker容器启动时,会自动执行CMD指令指定的命令或者脚本
使用CMD指令的语法如下:
CMD ["executable","param1","param2"]
其中,executable是要执行的命令或者脚本,param1和param2是可选的参数
例如:
CMD ["nginx", "-g", "daemon off;"]
该指令会在Docker容器启动时自动执行nginx -g daemon off;
命令
🔘 ENTRYPOINT
ENTRYPOINT指令用于设置Docker容器的入口点,即Docker容器启动时要执行的命令或脚本。
使用ENTRYPOINT指令的语法如下:
ENTRYPOINT ["executable","param1","param2"]
例如:
ENTRYPOINT ["nginx"]
该指令会将nginx命令设置为Docker容器的入口点
🐳 .dockerignore 文件
在Docker CLI
将上下文发送给Docker守护进程
之前, 它会在上下文的根目录中查找一个名为.dockerignore
的文件. 如果该文件存在, CLI
将修改上下文以排除其中匹配模式的文件和目录. 这有助于避免
不必要地向守护进程发送大的
或敏感的
文件和目录, 因为可能使用ADD
或COPY
将它们误添加到镜像中
CLI 将.dockerignore
文件解释为一个换行符分隔
的模式列表, 类似于Unix shell
的glob
文件. 为了进行匹配, 将上下文的根视为工作目录和根目录. 例如, 模式/foo/bar
和foo/bar
都排除了PATH的foo
子目录中名为bar
的文件或目录, 或者是位于URL的git存储库根目录
中名为bar
的文件或目录. 两者都没有排除其他任何东西
如果.dockerignore
文件中的行以#
开头, 那么这一行被认为是注释,在CLI
解释之前被忽略
下面是一个示例.dockerignore文件:
# comment
*/temp*
*/*/temp*
temp?
该文件会导致以下构建行为:
规则 | 行为 |
# comment |
被忽略 |
*/temp* |
排除根目录下所有直接子目录中名称以temp 开头的文件和目录. 例如, 普通文件/somedir/temporary.txt 和目录/somedir/temp 都会被排除 |
*/*/temp* |
在根目录下两层的子目录中排除以temp 开头的文件和目录. 例如:/somedir/subdir/temporary.txt |
temp? |
排除根目录下以temp 为后缀名的文件和目录, 如/tempa 和/tempb |
匹配规则是使用Go的filepath.Match
完成的. 程序会预先删除首尾空白符, 然后使用Go的filepath.Clean
消除.
和..
, 预处理后为空的行将被忽略
除了Go的filepath.Match
规则之外, Docker还支持一个特殊的通配符字符串**
, 它可以匹配任意数量的目录(包括0). 例如 **/*.go
将排除所有目录中所有以.go
结尾的文件, 包括构建上下文的根目录
以!
(感叹号)开头的行可用于排除规则中的例外. 下面是一个使用此机制的.dockerignore
文件示例:
*.md
!README.md
➡️ 除了README.md
, 所有以.md
结尾的文件都被排除在构建上下文之外
!
的使用位置会影响行为: 匹配特定文件的 .dockerignore
的最后一行决定它是被包含还是被排除. 考虑下面的例子:
*.md
!README*.md
README-secret.md
➡️ 除了README-secret.md
之外, 上下文中不包含任何markdown
文件
再看看下面的例子:
*.md
README-secret.md
!README*.md
➡️ 所有的README文件都包含在内, 因为!README*.
包含README-secret.md
,并且被放到最后一行
👉 你甚至可以使用.dockerignore
文件来排除Dockerfile
和.dockerignore
文件. 这些文件仍然被发送给Docker守护进程, 因为Docker需要它们来完成工作. 但是ADD
和COPY
指令不会将它们复制到镜像中
👉 最后, 我们一般情况下希望指定在上下文中包含哪些文件, 而不是排除哪些文件. 要实现这一点, 可以指定*
作为第一个模式, 后面跟着一个或多个!
模式
🏠 回到主页