【云原生|Docker】06-dokcerfile详解

目录

前言

Dockerfile基础示例 

 Dockerfile简介

1.  Dockerfile概念 

2.  Dokcer镜像分层理解

​3.  Doker build构建原理 

Dockerfile参数解析 

1.  Dokcerfile组成 

2.  指令说明 

2.1  FROM引入基础镜像

2.2  LABEL

2.3   ENV

2.4  RUN

2.5  COPY

2.6  ADD

2.7  VOLUME 

2.8  EXPOSE

2.9  WORKDIR 

2.10  USER 

2.11  CMD

2.12  ENTRYPOINT

2.13 HEALTHCHECK

2.14   ONBUILD

Docker镜像上下文 


 前言

        上一章我们介绍了镜像的基础操作和如何使用docker  commit的方式来制作一个镜像,从我们制作openssh的镜像过程可以看出commit的方式显然不是最优的,然而docker官方给我们推荐dockerfile才是我们工作中最常用的镜像制作方式。这篇文章我们将详细介绍dockerfile的使用。

Dockerfile基础示例 

        还是以centos:7为基础构建一个openssh的镜像。 

[root@clinet openssh]# tree .
.
├── Dockerfile
└── keys
    ├── ssh_host_ecdsa_key
    ├── ssh_host_ed25519_key
    └── ssh_host_rsa_key
[root@clinet openssh]# 
[root@clinet openssh]# 
FROM    centos:7
LABEL   name=xiaohaizhou [email protected]
RUN     yum install  openssh-server -y
ADD     ./keys/ssh_host_ecdsa_key /etc/ssh/
ADD     ./keys/ssh_host_ed25519_key /etc/ssh/
ADD     ./keys/ssh_host_rsa_key /etc/ssh/
RUN     chown root:root /etc/ssh/ssh_host_* && chmod 0600 /etc/ssh/ssh_host_* \
        && useradd -s /bin/bash -m -d /home/xhz xhz && \
        echo 'xhz123'|passwd xhz --stdin
EXPOSE  22
CMD     [ "/usr/sbin/sshd","-D" ]
[root@clinet openssh]#

 生成镜像:

[root@clinet openssh]#docker build -t openssh:v2.1  .  (注意后面有个点)

[root@clinet openssh]# docker build -t openssh:v2.1  .
[+] Building 0.0s (11/11) FINISHED                                                                                                                                               
 => [internal] load build definition from Dockerfile                                                                                                                        0.0s
 => => transferring dockerfile: 564B                                                                                                                                        0.0s
 => [internal] load .dockerignore                                                                                                                                           0.0s
 => => transferring context: 2B                                                                                                                                             0.0s
 => [internal] load metadata for docker.io/library/centos:7                                                                                                                 0.0s
 => [1/6] FROM docker.io/library/centos:7                                                                                                                                   0.0s
 => [internal] load build context                                                                                                                                           0.0s
 => => transferring context: 395B                                                                                                                                           0.0s
 => CACHED [2/6] RUN     yum install  openssh-server -y                                                                                                                     0.0s
 => CACHED [3/6] ADD     ./keys/ssh_host_ecdsa_key /etc/ssh/                                                                                                                0.0s
 => CACHED [4/6] ADD     ./keys/ssh_host_ed25519_key /etc/ssh/                                                                                                              0.0s
 => CACHED [5/6] ADD     ./keys/ssh_host_rsa_key /etc/ssh/                                                                                                                  0.0s
 => CACHED [6/6] RUN     chown root:root /etc/ssh/ssh_host_* && chmod 0600 /etc/ssh/ssh_host_*         && useradd -s /bin/bash -m -d /home/xhz xhz &&         echo 'xhz123  0.0s
 => exporting to image                                                                                                                                                      0.0s
 => => exporting layers                                                                                                                                                     0.0s
 => => writing image sha256:9504798a1c2921cfb8f5e3921418c81f99e31b73891235e6717c644ba9a39d8b                                                                                0.0s
 => => naming to docker.io/library/openssh:v2.1                                                                                                                             0.0s
[root@clinet openssh]# 

 启动镜像验证:

[root@clinet openssh]# docker ps 
CONTAINER ID   IMAGE          COMMAND               CREATED          STATUS          PORTS     NAMES
4cb72b73e887   openssh:v2.1   "/usr/sbin/sshd -D"   17 minutes ago   Up 17 minutes   22/tcp    frosty_panini
[root@clinet openssh]# 
[root@clinet openssh]# 
[root@clinet openssh]# ssh [email protected]
[email protected]'s password: 
Last login: Sat Mar 25 02:55:08 2023 from gateway
[xhz@4cb72b73e887 ~]$ 

 Dockerfile简介

1.  Dockerfile概念 

         Dockerfile是一种文本文件,它包含了一系列用于构建Docker镜像的指令和参数。Dockerfile提供了一种声明式的方式来定义Docker镜像的构建过程,它可以用来自动化构建Docker镜像,减少手动操作的错误,快速重现镜像,方便部署和运行应用程序。


        每一条指令构建一层镜像,因此每一条指令的内容,就是描述该层镜像应当如何构建。

2.  Dokcer镜像分层理解

         Docker镜像是分层的,这是因为Docker的镜像构建过程是基于联合文件系统(Union File System)的。

联合文件系统是一种文件系统的组合,它能够将多个不同的文件系统合并成为一个虚拟的文件系统,使得这些文件系统上的文件和目录都能够在同一个层次结构下被访问到。在Docker中,每一层镜像都是基于上一层镜像的修改而来,这些修改可以是添加、删除、修改文件等操作。当镜像被构建完成后,所有的这些修改都会被保存为一个新的层,并且每个层都是只读的。

这种分层的设计使得Docker镜像具有以下优势:

  1. 节省存储空间:因为每个层都是只读的,所以多个镜像可以共享相同的层,从而减少存储空间的使用。

  2. 提高构建速度:如果镜像已经存在于本地,那么Docker可以直接使用这些层来构建新的镜像,从而避免重新下载或构建相同的层,提高了构建速度。

  3. 提高可维护性:由于镜像的每个层都是只读的,所以可以更加方便地管理和维护镜像。

3.  Doker build构建原理 

        当执行docker build命令时,Docker引擎会读取Dockerfile文件,并按照其中的指令逐步构建镜像。具体来说,Docker引擎会执行以下步骤:

  1. 从基础镜像开始构建:Dockerfile中的第一条指令是通常是FROM指令,用于指定基础镜像。Docker引擎会在本地或者远程仓库中查找该镜像,并在此基础上构建新的镜像。

  2. 逐条执行Dockerfile中的指令:Dockerfile中的每一条指令都会生成一个新的中间镜像,该镜像包含了该指令所描述的文件系统更改。Docker引擎会在前一条指令生成的中间镜像的基础上应用该指令,生成新的中间镜像。

  3. 最终生成镜像:当Dockerfile中的所有指令都执行完成后,Docker引擎会生成一个最终的镜像,该镜像包含了所有指令所描述的文件系统更改。

  4. 缓存机制:在执行每条指令时,Docker引擎会将其结果缓存起来。如果后续执行的指令与前面的指令没有改变文件系统,则Docker引擎会直接使用缓存中的结果,从而提高构建效率。

总的来说,Docker build的工作原理就是根据Dockerfile文件中的指令逐步构建一个新的Docker镜像,该过程中利用缓存机制提高构建效率。 

Dockerfile参数解析 

1.  Dokcerfile组成 

Dockerfile分为四部分:

  • 基础镜像信息
  • 维护者信息
  • 镜像操作指令
  • 容器启动时执行指令

2.  指令说明 

2.1  FROM引入基础镜像

        第一条指令必须为FROM指令。并且,如果在同一个Dockerfile中创建多个镜像时,可以使用多个FROM指令(每个镜像一次) 

格式:
  FROM <image>
  FROM <image>:<tag>
  FROM <image>@<digest>

示例:  
	FROM centos:7
注:
   tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像

 2.2  LABEL

        LABEL 指令用于为 Docker 镜像添加元数据,包括作者、版本、描述等信息。

LABEL 指令可以让用户更容易地了解一个 Docker 镜像的基本信息,也方便了在使用 Docker 时对镜像进行搜索和分类。(在早期的dockerfile中使用的是MAINTAINE,目前该指令已经弃用) 

格式:
    LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:
  LABEL name='xiaohaizhou' mail='[email protected]'
注:
  使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据
  之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。

2.3   ENV

        ENV 指令用于设置环境变量,这些变量可以在 Docker 容器中使用。

在 Dockerfile 中使用 ENV 指令可以帮助我们在构建镜像时动态地设置一些环境变量,以适应不同的部署环境。比如,我们可以设置数据库的连接字符串、服务器的主机名、日志文件的路径等。

使用 ENV 指令可以方便地将容器的配置信息和应用程序的代码分离开来,使得应用程序的可移植性更强,同时也方便了容器的维护和部署。

格式:
    #<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
    ENV <key> <value>  
 
    #可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行#
    ENV <key1>=<value1> <key2>=<value2> ...

示例:
    ENV TZ  "Asia/Shanghai"
    ENV TERM  xterm
​    ENV  k1=v1  k2=v2

2.4  RUN

        Dockerfile 中的 RUN 指令用于在 Docker 镜像中执行命令或脚本。使用 RUN 指令可以在 Docker 镜像构建过程中自动化地执行一些操作,比如安装软件包、配置环境变量、创建用户等。

RUN 指令可以执行任何可以在 Linux 终端中执行的命令或脚本,如 apt-getyumpipgit clonecurl 等等。

注意,每个 RUN 指令都会在 Docker 镜像中创建一个新的中间层镜像,这些中间层镜像可以被后续的指令使用,但也会增加镜像的大小。因此,我们应该尽可能地将多个命令合并为一个 RUN 指令,以减少中间层镜像的数量和镜像的大小。

格式:
    # 前者将在shell终端中运行命令,即/bin/sh -c;后者使用exec执行。每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时,可以使用\来换行。(为了方便我们经常只使用第一种方式)
    RUN <command>

    RUN ["executable","param1","param2"]

示例:
    RUN apt-get update && apt-get install -y curl
    RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
    RUN apt-get install -y nodejs

 2.5  COPY

        COPY 指令用于将本地文件或目录复制到 Docker 镜像中。使用 COPY 指令可以将需要的文件或目录添加到 Docker 镜像中,以供容器在运行时使用。

COPY 指令将从构建上下文目录中 <src>的文件或目录复制到新的一层的镜像内的 <dest> 位置。<src>可以是多个,甚至可以是通配符,其通配符规则需要满足golang的filepath.Match规则。

<dest> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。

此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。

注意:

 COPY 指令只能复制本地的文件或目录到 Docker 镜像中,不能复制网络上的文件。如果需要从网络中下载文件并复制到 Docker 镜像中,可以使用 RUN 指令执行 curlwget 命令下载文件,然后再使用 COPY 指令将下载好的文件复制到 Docker 镜像中。

格式:

#--chown=<user>:<group> 是可选参数,用于指定复制的文件或目录的用户和组,<src> 是要复制的本地文件或目录的路径,<dest> 是要复制到 Docker 镜像中的路径。
    COPY [--chown=<user>:<group>] <src>... <dest>
    

示例:
    COPY package.json /usr/src/app/
    COPY hom* /mydir/
    COPY hom?.txt /mydir/

2.6  ADD

        ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。

 <src> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <dest> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。

 <src>为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <dest> 去。如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令。在docker官方的最佳实践中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是需要自动解压缩的场合。

格式:

# --chown=<user>:<group> 是可选参数,用于指定复制的文件或目录的用户和组,<src> 是要复制的本地文件或目录的路径,<dest> 是要复制到 Docker 镜像中的路径。

    ADD [--chown=<user>:<group>] <src>... <dest>

示例:

    ADD https://example.com/hello.tar.gz /app
    ADD test.tar.gz /app

2.7  VOLUME 

        容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在Dockerfile中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。相当于容器启动时使用的-v选项,只不过这里不能指定挂载到宿主机的位置。 

格式:
    VOLUME </path/to/dir>
    VOLUME ["<path1>", "<path2>"...]

示例:
    VOLUME /myvolume
    VOLUME ['/data']

2.8  EXPOSE

        EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

        要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。

 比如:像上面我们创建的openssh的dockerfile中我们可以通过expose来声明80端口,但是实际上80端口并不会开启,我们也不能通过80端口来进行远程访问。

格式:
    EXPOSE <port> [<port>...]

示例:
    EXPOSE 80 443   
    EXPOSE 11211/tcp 11211/udp

2.9  WORKDIR 

        使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。 

格式:
    WORKDIR /path/to/workdir

示例:
    # 最终路径为/a/b/c
    WORKDIR /a
    WORKDIR b
    WORKDIR c

        通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都会在该目录下执行。在使用docker run运行容器时,可以通过-w参数覆盖构建时所设置的工作目录。 

2.10  USER 

        USER 指令和WORKDIR相似,都是改变环境状态并影响以后的层。WORKDIR是改变工作目录,USER则是改变之后层的执行RUN, CMD 以及ENTRYPOINT这类命令的身份。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户。

格式:  
    USER <username>
 
示例:      
     USER www
 注:
  使用USER指定用户后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT都将使用该用户。
  镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。

2.11  CMD

Docker不是虚拟机,事实上容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。

在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如ubuntu镜像默认的CMD是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入bash。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu  cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。

在指令格式上,一般推荐使用exec方式执行,这类格式在解析时会被解析为JSON数组,因此一定要使用双引号 ",而不要使用单引号。

格式:
    CMD ["executable","param1","param2"]    #使用exec执行,推荐的方式
    CMD command param1 param2   #在/bin/sh中执行,提供给需要交互的应用
    CMD ["param1","param2"]    #提供给ENTRYPOINT的默认参数

示例:
    CMD ["/usr/sbin/sshd","-D"]

如果使用/bin/sh方式执行的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如

CMD echo $HOME

在实际执行中,会将其变更为:

CMD [ "sh", "-c", "echo $HOME" ]

注意:

        每个Dockerfile只能有一条CMD命令。如果指定了多条,只有最后一条会被执行。

2.12  ENTRYPOINT

ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。

ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint来指定。

当指定了ENTRYPOINT后,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:

<ENTRYPOINT> "<CMD>"

格式:
    ENTRYPOINT ["executable","param1","param2"]
    ENTRYPOINT command param1 param2 (shell中执行)

示例:
    ENTRYPOINT ["ls", "/usr/local"]
    CMD ["/usr/local/tomcat"]

思考一下:那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?这种 <ENTRYPOINT> "<CMD>"有什么好处呢?

 后面章节将会详细说说ENTRYPOINT  与CMD的区别

2.13 HEALTHCHECK

        HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。

        在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。

        而自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。

当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。

HEALTHCHECK 支持下列选项:

  • --interval=<duration>:定义健康检查的间隔时间,默认值为 30s。
  • --timeout=<duration>:定义健康检查的超时时间,默认值为 30s。
  • --start-period=<duration>:定义容器启动后的等待时间,以便在执行健康检查之前,容器有时间启动。默认值为 0s。
  • --retries=<number>:定义容器未通过健康检查的重试次数,默认值为 3。

和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。

HEALTHCHECK [options] CMD后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。

格式:
    HEALTHCHECK [options] CMD <command>     #设置检查容器健康状况的命令
    HEALTHCHECK NONE  #如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

示例:假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断:

# 这里我们设置了每5秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过3秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令。

FROM nginx
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

为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,可以用 docker inspect 来查看:

docker  inspect  web   | grep jq .[].State.Health

2.14   ONBUILD

        ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。

Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。所以该指令我们不常用。

格式: 
	ONBUILD [INSTRUCTION]
示例:
  ONBUILD ADD . /app/src
  ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注:
  NNBUID后面跟指令,当当前的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被钥触发

Docker镜像上下文 

 构建镜像指令:

# mysoft/centos:6.6为新生成的镜像的标签,"."为dockerfile所在路径。

 docker   build   -t  mysoft/centos:6.6 .

 在上使用docker build来构建镜像的时候,在命令的最后面加了个".",我们直接解释为当前目录,即dockerfile所在的目录。其实这种表述是并不准确的。事实上这是在指定上下文路径。那么什么是上下文呢? 

        首先我们要理解docker build的工作原理:

        docker在运行时分为Docker引擎(也就是服务端守护进程)和客户端工具。Docker引擎提供了一组REST API,被称为Docker Remote API。而docker命令这样的客户端工具,则是通过这组api与docker引擎交互,从而完成这种功能。因此,虽然表面上我们好像是在本机执行各种docker功能,但实际上,一切都是使用的远程调用形式在服务端(Docker引擎)完成。也因为这种C/S设计,让我们操作远程服务器的Docker引擎变得轻而易举。 

当我们进行镜像构建的时候,并非所有的定制都会通过RUN指令完成,经常会需要将一些本地文件复制进镜像,比如通过COPY、ADD等指令。而docker build 命令构建镜像,其实并非在本地,而是在服务端,也就是Docker引擎中构建的。那么在这种C/S架构中,如何 才能让服务端获得本地文件呢? 

这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build命令得知这个路径后,会将路径下的所有内容打包,然后上传给Docker引擎。这样Docker引擎收到这个上下文包后,展开就会获得构建镜像所需的所有文件。

如果在Dockerfile中这么写:

COPY   ./package.json   /app/

 这并不是要复制执行docker build命令所在的目录下的package.json,也是不复制Dockerfile所在目录下的package.json,而是复制上下文目录中的package.json。

因此,COPY这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么COPY ../packeg.json /app或者COPY /opt/xxx /app/这种指令无法工作的原因,因为这些路径已经超出了上下文的范围,Docker引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。

现在就可以理解docker build -t mysoft/centos:6.6 .中的这个".",实际是在指定上下文的目录,docker build命令会将该目录下的内容打包交给docker引擎以帮助构建镜像。

理解构建上下文对镜像构建是很重要的,可以避免犯一些不应该的错误。比如有些初学者在发现COPY /opt/xxx /app/不工作后,干脆将Dockerfile放到了根目录去构建。结果发现docker build执行后,在发送一个几十GB的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让docker build打包整个硬盘,这显然错误的使用方法。

一般来说,应该会将Dockerfile置于一个空目录下,如果该目录下没有所需的文件,那么应该把所需的文件复制一份过来。如果目录下有些东西确实不希望构建时传递给Docker引擎,那么可以使用和.gitignore一样的语法写一个.dockerignore,该文件是用于剔除不需要作为上下文传递给Docker引擎的文件。

那么为什么会有人误以为"."是指定Dockerfile所在目录的呢? 这是因为在默认情况下,如果不额外指定Dockerfile的话,会将上下文目录下的名为Dockerfile的文件作为Dockerfile。这只是默认行为,实际上Dockerfile的文件名并不要求必须为Dockerfile,并且并不要求必须位于上下文目录中,比如可以使用-f ../Dockerfile.py参数指定某个文件为Dockerfile。当然一般大家习惯性的会使用默认的文件名Dockerfile,以及会将其置于镜像构建上下文目录中。

总结:

        我们在编写Dokcerfile的时候一般会将Dockerfile置于一个空的文件夹中,也会将制作镜像所需要传入的文件或压缩包放在Dockerfile的同级目录。build的'.'指代的上下文就是Dockerfile所在的目录,这也有效的避免了在构建时传入一些不必要的文件,导致构建镜像过大。


猜你喜欢

转载自blog.csdn.net/qq_43714097/article/details/129743902