Dockerfile概述与用法
本文最后更新于:1 年前
前言
学习本文需要一些了解Docker的概念以及一些名词。
一、Docker概述
1、Docker简介
Docker 镜像是通过读取Dockerfile来构建镜像文件的
。Dockerfile是一个文本文档,它包含用户可以在命令行上调用的所有命令来组装镜像,每条指令都是独立运行的,并会创建一个新的镜像层
。使用docker build 命令用户用户可以创建一个自动构建,该构建可以连续执行几个命令行指令。
2、为什么要用Dockerfile?
为什么要用Dockerfile?这个问题的本身其实是说为什么我们要自定义镜像,明明Docker Hub上有这么多镜像可以用,我们还要自己费心思做镜像。
主要原因:Docker Hub上许多官方镜像只是基础包,很多功能都没有,需要我们自己对官方镜像做扩展,以打包成我们生产应用的镜像。
参考我的第一篇文章Docker入门篇:《Docker的入门与安装》
二、docker build工作原理
1、镜像构建过程
docker build命令从 Dockerfile 和 context 构建一个镜像。构建的 context 是位于指定位置PATH或URL的一组文件。PATH是本地文件系统上的一个目录。URL是Git存储库位置。
1 |
|
完整的镜像架构图:构建context是递归处理的。每条指令都是独立运行的,并会创建一个新的镜像层。
例子中docker build后面使用(.)表示当前目录,作为指定context的路径:
1 |
|
构建由 Docker 守护程序运行,而不是由 CLI 运行
。构建过程所做的第一件事是将整个 context(递归)发送到守护进程。
2、context(上下文)
Docker使用客户端-服务器架构
。Docker客户端 与 Docker守护进程 通信,守护进程 负责构建、运行和分发Docker容器。Docker客户端 和 守护进程 可以运行在同一个系统上,或者将 Docker客户端 连接到远程的Docker守护进程。Docker客户端 和 守护进程 通过UNIX套接字或网络接口使用REST API进行通信
。另一个 Docker客户端 是Docker Compose,它允许使用由一组容器组成的应用程序。
Docker客户端(Docker) 是许多Docker用户与Docker交互的主要方式。使用docker 命令时,则是通过docker API 与 Docker守护进程进行交互,从而完成各种功能,Docker客户端 可以与多个 守护进程 通信。因此,虽然表面上我们是在本机执行各种 docker 功能,但实际上,客户端会将这些命令发送到 Docker守护进程 执行它们
。也因为这种 C/S 设计,让我们操作远程服务器的 Docker守护进程 变得轻而易举。
当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker守护进程构建的。
那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
这就引入了
context(上下文)
的概念。当构建的时候,用户会指定构建镜像context(上下文)
的路径,docker build
命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker守护进程。这样Docker守护进程收到这个context(上下文)
包后,展开就会获得构建镜像所需的一切文件。
如果要在构建上下文中使用文件,Dockerfile引用指令中指定的文件,例如COPY指令。要提高构建的性能,可以通过在context目录中添加.dockerignore文件来排除文件和目录。
传统上,Dockerfile位于上下文的根目录中。实际上,Dockerfile 的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,在docker build中使用-f标志来指向文件系统中的任何位置的Dockerfile。
一般大家习惯性的会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。
注意:不要将根目录
/
用作PATH构建上下文,因为它会导致构建将硬盘驱动器的全部内容传输到 Docker 守护程序。
三、Dockerfile指令
1、约定
指令必须大写
,以便更容易地将它们与参数区分开来。Dockerfile必须以FROM指令开始
。- 以# 开头视为注释。
2、FROM
- 作用:指定基础镜像,Dockerfile必须以FROM指令开始。
- 格式:
1 |
|
- 说明:
Docker Hub中的大部分镜像都是从基础镜像 FROM scratch构建的,在基础镜像之上构建软件和配置
。FROM指令初始化一个新的构建阶段,并为后续指令设置基本镜像。因此,一个有效的Dockerfile必须以一个FROM指令开始。
镜像可以是任何有效的镜像—从Docker Hub提取镜像尤其容易。
3、RUN
- 作用:构建镜像时需要执行的命令
- 格式:
1 |
|
- 说明:
镜像构建的时候需要运行的命令,有两种命令执行方式。RUN指令将在当前镜像之上的新层中执行任何命令并提交结果。生成的提交镜像将用于Dockerfile。
注意:在下一次构建期间,
RUN
指令的缓存不会自动失效。像RUN apt-get distupgrade -y
这样的指令的缓存将在下一次构建期间被重用。RUN指令的缓存可以通过使用--no-cache
标志来失效,例如docker build——no-cache
。
4、CMD
- 作用:在构建镜像之后调用,容器启动时调用命令
- 格式:
1 |
|
- 说明:
CMD的主要目的是为容器提供默认的执行命令。
包括可执行文件,也可以省略可执行文件,在这种情况下,您必须同时指定一个ENTRYPOINT指令。
注意:
CMD
不同于RUN
,CMD
在构建时不执行任何操作,CMD
是容器启动时执行的指令,RUN
是镜像构建时执行的指令。
5、ENTRYPOINT
- 作用:在构建镜像之后调用,容器启动时调用命令
- 格式:
1 |
|
- 说明:
ENTRYOINT与CMD作用一样,都是在容器运行时执行命令,两者都是重要的指令。
注意:
ENTRYPOINT
与CMD
非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给CMD。Dockerfile中只允许有一个ENTRYPOINT命令
,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。
通常情况下, ENTRYPOINT 与CMD一起使用,ENTRYPOINT 写默认命令,当需要参数时候 使用CMD传参。
6、CMD vs ENTRYPOINT
《Docker官方文档》
《Dockerfile 的 CMD 与 ENTRYPOINT 傻傻分不清楚》
《ENTRYPOINT vs CMD: Back to Basics》
7、LABEL
- 作用:LABEL指令向镜像添加元数据。
- 格式:
1 |
|
- 说明:
LABEL指令向镜像添加元数据。LABEL是一个键-值对
。要在LABEL值中包含空格,请在命令行解析中那样使用引号和反斜杠。
注意:
基础或父镜像(FROM行中的镜像)中包含的标签由你的镜像继承。如果标签已存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值
。
8、MAINTAINER (已弃用)
- 作用:MAINTAINER指令设置生成的镜像的作者信息。
- 格式:
1 |
|
9、EXPOSE
- 作用:EXPOSE指令告诉Docker容器在运行时
监听指定的网口
。 - 格式:
1 |
|
- 说明:
EXPOSE指令告诉Docker容器在运行时监听指定的网口。可以指定端口侦听的协议类型是TCP还是UDP,如果不指定协议类型,默认为TCP
。
注意:
如果没有发布端口,后期也可以通过-p 8080:80方式映射端口,但是不能通过-P形式映射
10、ENV
- 作用:ENV指令将设置环境变量。
- 格式:
1 |
|
- 说明:
ENV
当容器从生成的图像运行时,使用ENV设置的环境变量将一直存在。你可以使用 docker inspect
查看这些值,并使用 docker run --env < key >=< value >
更改它们。
注意:
ENV
指令还允许使用另一种语法ENV < key > < value >
,省略=
,例如:
ENV MY_VAR my-value
11、ADD
- 作用:将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget。
- 格式:
1 |
|
- 说明:
该ADD
指令从<src>
路径复制新文件、目录或远程文件 URL,并将它们添加到镜像的文件系统中<dest>
。<src>
可以指定多个资源,但如果它们是文件或目录,则它们的路径被解释为相对于构建context(上下文)的源。
ADD遵守以下规则:
<src>
路径必须在构建的上下文中;你不能ADD ../something /something
,因为docker build
的第一步是将上下文目录(和子目录)发送到 docker 守护进程。- 如果
<src>
是一个URL,且<dest>
不是以斜杠结尾,则从该URL下载文件并复制到<dest>
。- 如果
<src>
是一个URL,且<dest>
是以斜杠结尾,则从 URL 推断文件名并将文件下载到<dest>/<filename>
.。例如:ADD http://example.com/foobar /
将创建文件/foobar
。 URL 必须有一个重要的路径,以便在这种情况下可以找到适当的文件名(http://example.com
将不起作用)。- 如果
<src>
是目录,则复制目录的全部内容,包括文件系统元数据。- 如果
<src>
是可识别压缩格式(identity、gzip、bzip2 或 xz)的本地tar 存档,则将其解压缩为目录。来自远程URL 的资源不会被解压缩
。当一个目录被复制或解包时,它的行为与 相同tar -x
。
- 文件是否被识别为可识别的压缩格式完全取决于文件的内容,而不是文件的名称。例如,如果一个空文件恰好以此结尾,.tar.gz
则不会被识别为压缩文件,也不会生成任何类型的解压缩错误消息,而是将文件简单地复制到目标位置。- 如果
<src>
是任何其他类型的文件,它将与它的元数据一起被单独复制。在这种情况下,如果<dest>
以斜杠结尾/,它将被视为一个目录,其内容<src>
将写入<dest>/base(<src>)
。<src>
直接指定了多个资源,或者使用了通配符,则<dest>
必须是目录,并且必须以斜杠结尾/
。- 如果
<dest>
不以斜杠结尾,它将被认为是一个普通文件,其<src>
的内容将写入<dest>
。- 如果
<dest>
不存在,它会连同其路径中所有缺失的目录一起创建。
12、COPY
- 作用:将本地文件添加到容器中,但是是不会自动解压文件,也不能访问网络资源。
- 格式:
1 |
|
- 说明:
COPY指令从路径复制新文件或目录<src>
并将它们添加到容器的文件系统中<dest>
。<src>
可以指定多个资源,但文件和目录的路径将被解释为相对于构建上下文的源。
COPY遵守以下规则:
<src>
路径必须在构建的上下文中;你不能ADD ../something /something
,因为docker build
的第一步是将上下文目录(和子目录)发送到 docker 守护进程。- 如果
<src>
是目录,则复制目录的全部内容,包括文件系统元数据。- 如果
<src>
是任何其他类型的文件,它将与它的元数据一起被单独复制。在这种情况下,如果<dest>
以斜杠结尾/,它将被视为一个目录,其内容<src>
将写入<dest>/base(<src>)
。<src>
直接指定了多个资源,或者使用了通配符,则<dest>
必须是目录,并且必须以斜杠结尾/
。- 如果
<dest>
不以斜杠结尾,它将被认为是一个普通文件,其<src>
的内容将写入<dest>
。- 如果
<dest>
不存在,它会连同其路径中所有缺失的目录一起创建。
13、VOLUME
- 作用:用于指定持久化目录(指定此目录可以被挂载出去)
- 格式:
1 |
|
- 说明:
VOLUME指令创建具有指定名称的挂载点,并将其标记为保存来自本机主机或其他容器的外部挂载卷。
参考《Docker卷(volumes)》
关于指定卷的注意事项:
基于 Windows 的容器上的卷
:使用基于 Windows 的容器时,容器内卷的目标必须是以下之一:
- 不存在或为空的目录
- C盘以外的驱动器从 Dockerfile 中更改卷
:如果任何构建步骤在声明卷后更改了卷中的数据,则这些更改将被丢弃。JSON 格式
:列表被解析为 JSON 数组。必须用双引号 ( “) 而不是单引号 ( ‘) 将单词括起来。主机目录在容器运行时声明
:主机目录(挂载点)本质上是依赖于主机的。这是为了保持镜像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。由于这个原因,你不能从Dockerfile内挂载主机目录。VOLUME
指令不支持指定host-dir
参数。在创建或运行容器时,必须指定挂载点
。
14、USER
- 作用:设置用户名(或 UID)和可选的用户组(或 GID)。
- 格式:
1 |
|
- 说明:
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户。
注意:
在 Windows 上,如果用户不是内置帐户,则必须先创建用户。这可以通过net user作为 Dockerfile 的一部分调用的命令来完成。
1 |
|
15、WORKDIR
- 作用:设置工作目录。
- 格式:
1 |
|
- 说明:
WORKDIR
设置工作目录,类似于cd命令。设置工作目录后,Dockerfile中其后的命令RUN
、CMD
、ENTRYPOINT
、ADD
、COPY
等命令都会在该目录下执行。如果不存在,即使它没有在任何后续指令中使用,它也会被创建。
注意:
Dockerfile里WORKDIR
指令使用之前设置的环境变量ENV
,例如:
1 |
|
最终
pwd
命令的输出Dockerfile将是/path/$DIRNAME
。
如果未指定,默认工作目录为/
。在实践中,如果你不是从头开始构建Dockerfile,WORKDIR
可能由你正在使用的基础镜像设置。
因此,为了避免在未知目录中进行意外操作,最好是显式设置WORKDIR
。
16、ARG
- 作用:用于指定传递给构建运行时的变量(给Dockerfile传参),相当于构建镜像时可以在外部为里面传参
- 格式:
1 |
|
- 说明:
ARG
指令定义了一个变量,用户可以在构建时通过使用 标志的docker build
命令将其传递给构建器。
注意:
不建议使用构建时变量来传递 github 密钥、用户凭据等机密信息
。使用该docker history
命令的映像的任何用户都可以看到构建时变量值。
1)默认值
ARG
指令可以设置默认值:
1 |
|
如果ARG
指令具有默认值并且在构建时没有传递任何值,则构建器将使用默认值。
2)使用 ARG 变量
可以使用ARG
或ENV
指令指定RUN
指令可用的变量。使用ENV
指令定义的环境变量总是覆盖同名的ARG
指令。
带有ENV
和 ARG
指令的 Dockerfile
例子:
1 |
|
镜像是用这个命令构建的:
1 |
|
RUN
指令使用v1.0.0而不是ARG
用户传递的设置。
17、ONBUILD
- 作用:向镜像添加了一条触发指令。
- 格式:
1 |
|
- 说明:
ONBUILD指令向镜像添加了一条触发指令。
NNBUID后面跟指令,当该镜像被用作另一个构建的基础镜像时,触发器将在其构建的上下文中执行。就好像它是FROM 在指令之后立即插入的一样Dockerfile。
如果你正在构建一个镜像,该镜像将用作构建其他镜像的基础,例如可以特定于用户的配置定制的应用程序构建环境或守护进程。
注意:
ONBUILD
指令可能不会触发FROM
或MAINTAINER
指令。
18、Dockerfile常用指令
指令 | 描述 |
---|---|
FROM | 构建新镜像使用的基础镜像 |
MAINTAINER(已弃用) | 构建镜像的作者或邮件地址 |
RUN | 构建镜像时执行命令 |
COPY | 拷贝文件或目录到镜像中 |
ENV | 设置环境变量 |
USER | 为RUN、CMD和ENTRYPOINT等执行命令指定运行用户 |
EXPOSE | 声明容器运行的服务端口 |
WORKDIR | 为RUN、CMD、ENTRYPOINT、COPY和ADD设置工作目录 |
ENTRYPOINT | 运行容器时执行,如果由多个ENTRYPOINT指令,最后一个生效,可以追加命令 |
CMD | 运行容器时执行,如果由多个CMD 指令,最后一个生效,可被替代 |
LABEL | 设置镜像的标签 |
VOLUME | 设置容器的挂载卷 |
ARG | 指令定义了一个变量 |
ONBUILD | 向镜像添加了一条触发指令 |
四、构建自己的镜像
1、构建镜像步骤
构建镜像步骤:
- 编写一个
Dockerfile
文件 - 通过
docker build
命令构建成一个镜像 docker run
命令运行镜像docker push
命令发布镜像到Docker Hub
注意:
- 如果有多个
RUN
,自上而下依次运行,每次运行都会形成新的层,建议&& 放入一行运行- 如果有多个
CMD
,只有最后一个运行- 如果有多个
ENTRYPOINT
,只有最后一个运行- 如果
CMD
和ENTRYPOINT
共存,只有ENTRYPOINT
运行,且最后的CMD
会当做ENTRYPOINT
的参数
2、编写Dockerfile文件
1 |
|
逐行解释该
Dockerfile
文件的指令:
FROM centos
:该image文件继承官方的centos,他会先在你本地寻找centos镜像ENV MYPATH /usr/local
:设置环境变量MYPATHWORKDIR $MYPATH
:直接使用上面设置的环境变量,指定/usr/local为工作目录RUN yum -y install vim && RUM yum -y install net-tools
:在/usr/local目录下,运行yum -y install vim和yum -y install net-tools命令安装工具,注意安装后的所有依赖和工具都会打包到image文件中EXPOSE 80
·:将容器80端口暴露出来,允许外部连接这个端口CMD
:指定容器启动的时候运行命令
- CMD echo $MYPATH:输出MYPATH环境变量
- CMD echo “—–end—-“:输出—–end—-
- CMD /bin/bash:进入/bin/bash命令行
3、执行build命令构建镜像
执行build命令生成image文件。
1 |
|
如果出现Error: Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist
错误,不要惊慌!
报错信息的意思是:从仓库 ‘appstream’ 下载元数据失败:由于镜像列表中没有 URL,不能准备内部镜像列表。
错误原因:
- 可能的情况便是网络连接问题。检查是否可以连接外部网络,可以使用
ping baidu.com
查看是否有丢包情况。如果丢包,则进一步检查网络连接是否正常;如果没有丢包,继续阅读下文- 第二种情况,便是 CentOS 已经停止维护的问题。2020 年 12 月 8 号,CentOS 官方宣布了停止维护 CentOS Linux 的计划,并推出了 CentOS Stream 项目,CentOS Linux 8 作为 RHEL 8 的复刻版本,生命周期缩短,于 2021 年 12 月 31 日停止更新并停止维护(EOL)。
解决Error: Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist
错误
因为我们的
Dockerfile
的基础镜像是可以基于本地的镜像,所以我们只需要修改本地的centos
镜像,再用我们的commit
指令生成新的镜像用来当我们Dockerfile
的基础镜像commit指令
参考文章《Docker镜像概述和分层原理》
- 运行centos镜像,并进入交互界面
1 |
|
- 进入到 yum 的 repos 目录
1 |
|
- 修改 centos 文件内容
1 |
|
- 生成缓存更新(第一次更新,速度稍微有点慢,耐心等待两分钟左右)
1 |
|
- 运行 yum update (更新的东西很多,大约五分钟)
1 |
|
出现Complete的就是成功了
- 使用
docker commit
将容器保存为新的镜像
1 |
|
- 修改一下我们的Dockerfile里的FROM基础镜像这块
1 |
|
- 重新执行
build
命令
1 |
|
构建centos镜像成功!
4、测试运行
1)使用 docker history
镜像id 查看镜像构建过程
1 |
|
2)运行容器,看看是否能够执行ifconfig
及vim
命令
1 |
|