目录

Docker - Dockerfile

# 什么是Dockerfile

Dockerfile 可以认为是 Docker 镜像的描述文件,是由一系列命令和参数构成的脚本。主要作用是用来构建 docker 镜像的构建文件。

更直接的理解:Dockerfile 就是一个文件的名字,如 Java 文件叫 xxx.java;Dockerfile 文件就叫 Dockerfile

image-20211121221858094

# 解析过程

image-20211121225615709

# 基本知识

  • 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  • 指令按照从上到下,顺序执行
  • # 表示注释
  • 每条指令都会创建一个新的镜像层,并对镜像进行提交

# 大致流程

Dockerfile 是用来构建 docker 容器的,它是由一系列的指令和参数构成,通过 build dockerfile 可以得到镜像。Dockerfile 的每一个保留字指令 必须大写,后面至少跟一个参数,它的解析过程:首先拉取基础镜像,当执行一条指令的时候,基于基础镜像运行容器,对容器进行修改,然后 commit 新的镜像层;当再次执行指令的时候,基于新镜像运行容器,对容器进行修改 commit 新的镜像层,这样反复产生镜像(「不完整镜像」),最终只会获得一个完整的镜像。而原先产生的「不完整镜像」存入 Cache(缓存)里,如果再次构建镜像,则不需要从头开始,直接从缓存里找到需要的「不完整镜像」。

# Dockerfile、镜像、容器的理解

从应用软件的角度来看,Dockerfile、Docker 镜像、Docker 容器分别代表着三个不同的阶段:

  • Dockerfile 是软件的基本代码
  • Docker 镜像是软件的交付品
  • Docker 容器可以认为是软件的运行态
  • Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器负责运维和部署,三者缺一不可

# 指令表

官方文档:https://docs.docker.com/engine/reference/builder/ (opens new window)

关键字 说明 小写
FROM 基础镜像,当前新镜像是基于哪个镜像的。第一个指令必须是 FROM from
MAINTAINER 镜像维护者的姓名和邮箱地址(废弃) maintainer
RUN 构建容器时需要运行的命令 run
EXPOSE 当前容器对外暴露出的端口 expose
WORKDIR 指定在创建容器后,终端默认登录的进来工作目录,一个落脚点 workdir
ENV 用来在构建镜像过程中设置环境变量 env
ADD 将宿主机目录下的文件拷贝进镜像且 ADD 命令会自动处理 URL 和解压 tar 压缩包 add
COPY 类似 ADD,拷贝文件和目录到镜像中
将从构建上下文目录中<原路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置
copy
VOLUME 容器数据卷,用于数据保存和持久化工作 volume
CMD 指定一个容器启动时要运行的命令
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
cmd
ENTRYPOINT 指定一个容器启动时要运行的命令
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及其参数
entrypoint
ONBUILD 当构建一个被继承的 DockerFile 时运行命令,父镜像在被子镜像继承后,父镜像的 ONBUILD 被触发 onbuild

# 环境变量

在 Dockerfile 中,环境变量是以 $variable_name${variable_name} 表示,它们被同等对待,并且大括号通常用于解决没有空格的变量名称的问题。

${variable_name} 语法还支持 bash 以下指定的一些标准修饰符:

  • ${variable:-word} 表示如果 variable 设置,则结果将是该值。如果 variable 未设置,word 则将是结果。
  • ${variable:+word} 表示如果 variable 设置则为 word 结果,否则为空字符串。

在所有情况下,word 可以是任何字符串,包括额外的环境变量。

如果只是单纯使用 $ 而不是作为变量使用,请加上 \ 进行转义。例如:\$foo\${foo},将分别转换为 $foo${foo} 文字。

# 指令使用

笔记

本内容使用 centos 7 版本的镜像进行测试。

顺便一提的是,上下文指的是 Dockerfile 文件所在的目录(根目录),请把指令操作的根目录当作上下文的源。

2021-11-22 @Young Kbt












 

# 拉取centos 7 的镜像命令
docker pull centos:centos7

# 查看镜像
docker images

# 返回结果
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
tomcat        8.5.73    7ec084df520c   3 days ago     249MB
mysql         latest    b05128b000dd   4 days ago     516MB
hello-world   latest    feb5d9fea6a5   8 weeks ago    13.3kB
centos        7         eeb6ee3f44bd   2 months ago   204MB
1
2
3
4
5
6
7
8
9
10
11
12

# build指令

这是 docker 的指令,并非是 Dockerfile 的指令,但是和 Dockerfile 关系很大。

这是将 Dockerfile 打包成镜像的指令。

语法:

# 语法格式
docker build [options] <自定义镜像名>[:tag] <Dockerfile 路径>

# 先指定 tag 版本,再指定 Dockerfile 所在路径
docker build -t <自定义镜像名:tag> <Dockerfile 路径>

# 先指定 Dockerfile 所在路径,再指定 tag 版本
docker build -f <Dockerfile 的路径> -t <自定义镜像名:tag>
1
2
3
4
5
6
7
8

常用的是第 5 行的打包方式。

例子 1:直接打包在 /opt 下的 Dockerfile 文件,版本为 1.0,名字叫 kele

docker build -t kele:1.0 /opt
1

不需要写到 Dockerfile 文件名,它默认会找到这个文件

例子 2:利用 -f 打包在 /opt 下的 Dockerfile 文件,版本为 1.0,名字叫 kele

docker build -f /opt/Dockerfile -t kele:1.0 .
1

# FROM指令

基于那个镜像进行构建新的镜像,在构建时会自动从 docker hub 拉取 base 镜像或者本地拉去镜像(优先本地)。必须作为 Dockerfile 的第一个指令出现,该指令可以出现多次,但是仍以最后一条 FROM 为准。

语法:

FROM <image> [AS <name>]    # 默认版本是 latest
FROM <image>[:<tag>] [AS <name>]    # 当版本不写 latest 时,请填写其他版本
FROM <image>[@<digest>] [AS <name>]  # 使用摘要
1
2
3

例子 1:创建 Dockerfile 文件,引用 centos 7

  • 首先在 /opt 下创建 docker-file 目录,专门来做测试

    cd /opt
    
    mkdir docker-file
    
    cd docker-file
    
    1
    2
    3
    4
    5
  • 创建 Dockerfile 文件,并给文件添加 FROM 指令

    vim Dockerfile
    
    # 此时已经进入 Dockerfile 文件
    # 添加内容
    FROM centos:7
    
    1
    2
    3
    4
    5
  • 执行 build 指令,查看结果


     






     

    # 执行打包命令
    docker build -t mycentos7:1.0 .
    
    # 查看 centos 镜像
    docker images mycentos7
    
    # 返回结果
    REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
    mycentos7     1.0       eeb6ee3f44bd   2 months ago   204MB
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    . 代表当前目录。

# MAINTAINER指令

镜像维护者的姓名和邮箱地址(废弃)

语法:

MAINTAINER <name>
1

# RUN指令

RUN 指令将在当前映像之上的新层中执行任何命令并提交结果。生成的提交映像将用于 Dockerfile 中的下一步。

语法:(两种形式)

# shell 形式,命令在 shell 中运行,默认 /bin/sh -c 在 Linux 或cmd /S /CWindows 上
RUN <command>
# 如:
RUN echo hello

# 执行形式
RUN ["参数1", "参数2", "参数3", .....]
RUN ["executable", "param1","param2 "]

# 例子:
RUN /bin/bash -c echo hello
# 等价于
RUN["/bin/bash", "-c", "echo hello"]
1
2
3
4
5
6
7
8
9
10
11
12
13

在 shell 形式中,您可以使用 \(反斜杠)将单个 RUN 指令延续到下一行。例如,考虑以下两行:

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
1
2

它们一起相当于这一行:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
1

要使用除 /bin/sh 之外的不同 shell,请使用传入所需 shell 的 exec 形式。例如:

RUN["/bin/bash", "-c", "echo hello"]
1

重复 RUN 的指令会进入缓存里,下次再 RUN 该指令,直接去缓存获取结果。可以使用 --no-cache 标志使指令缓存无效,例如

docker build --no-cach <指令>
1

例子 1:给 centos 安装 vim 指令

  • 官方的 CentOS 并没有安装 vim 指令,进入官方的 CentOS 容器,执行 vim 指令,会报错

    # 启动 centos 7
    docker run -it centos:7
    # 使用 vim 指令
    [root@438f1c67afb1 /]# vim aa.txt
    
    # 没有安装
    bash: vim: command not found
    
    1
    2
    3
    4
    5
    6
    7
  • 我们在打包成镜像的时候,让其自动安装,在 Dockerfile 文件添加 RUN 指令

    有两种形式,我们使用任意一个即可(这是 Dockerfile 文件



     

     

    FROM centos:7
    
    RUN yum install -y vim
    # 另一种格式
    # RUN ["yum","install","-y","vim"]
    
    1
    2
    3
    4
    5
  • 打包镜像并验证结果

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 验证结果
    # 启动镜像
    docker run -it mycentos7:1.0
    
    # 使用 vim 指令
    [root@438f1c67afb1 /]# vim aa.txt
    
    # 直接创建 aa.txt 文件并进入
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    build 后的镜像如果在本地已经有了,则会覆盖本地的镜像

笔记

第一次执行 RUN 指令,会很慢,但是下一次重复执行该指令,就很快了,因为有缓存。

什么时候又变慢呢?RUN 指令发生了修改,那么缓存里旧的指令就被删除,存入新的指令。

2021-11-22 @Young Kbt

# EXPOSE指令

用来指定构建的镜像在运行为容器时 对外暴露端口

语法:

EXPOSE 端口号/类型   # 如果没有指定类型,则默认暴露都是 tcp
1

EXPOSE 指令 实际上并未发布端口。它充当构建镜像的人和运行容器的人之间的一种文档,关于打算发布哪些端口。要在运行容器时实际发布端口,请使用 -p 指令在 docker run 时来发布和映射一个或多个端口,或者使用 -P 指令来发布所有暴露的端口并将它们映射到高阶端口。

默认情况下,EXPOSE 指定 TCP。您还可以指定 UDP。

EXPOSE 80/tcp
EXPOSE 80/udp
1
2

在这种情况下,如果您使用 -P,则端口将为 TCP 公开一次,为 UDP 公开一次。请记住,-P 在主机上使用临时高阶主机端口,因此 TCP 和 UDP 的端口将不同。

无论 EXPOSE 设置如何,您都可以在运行时使用 -p 标志覆盖它们。例如:

docker run -p 80:80/tcp -p 80:80/udp ...
1

例子 1:暴露 8080 和 9090 端口

  • 在 Dockerfile 文件添加内容






    FROM centos:7
    
    EXPOSE 8080
    EXPOSE 9090/tcp
    
    1
    2
    3
    4

# WORKDIR指令

用来为 Dockerfle 中的任何 RUN、CMD、ENTPYPOINT、COPY 和 ADO 指令设置工作目录。如果 WORKDIR 不存在,即使它没有在任何后续 Docerile 指令中使用,它也将被创建。一旦创建了 WORKDIR 目录,启动容器并进入容器时,默认处于该目录下。

语法:

WORKDIR <绝对路径> | <相对路径>
1

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

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
1
2
3
4

最终 pwd 命令的输出 Dockerfile 将是 /a/b/c

WORKDIR 指令可以解析先前使用 ENV。你只能使用在 Dockerfile。例如:

ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
1
2
3

最终 pwd 命令的输出 Dockerfile 将是 /path/$DIRNAME

例子 1:添加工作目录 /data/kele

  • Dockerfile 文件添加内容






    FROM centos:7
    
    WORKDIR /data
    WORKDIR kele
    
    1
    2
    3
    4
  • WORKDIR 指定的目录,就是进入容器后所处的目录










     


    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# pwd
    
    # 返回结果
    /data/kele
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

# ENV指令

用来为构建镜像设置环境变量。这个值将出现在构建阶段中所有后续指令的环境中。

语法:

ENV <key> <value>
# 或者
ENV <key>=<value

# 例子:
ENV BASH_PATH /data/kele
# 使用 BASH_PATH 就是使用 /data/kele 目录
1
2
3
4
5
6
7

这个指令类似于 Java 的数据类型。

什么时候用呢?当 Dockerfile 文件里某一字符串出现率太高,可以将字符串赋值给一个变量,引用该变量即可(引用变量使用 $ 作为前缀)。

  • ENV 指令 允许 <key>=<value> ... 一次设置多个变量,例如:

    ENV MY_NAME="John Doe"
    ENV MY_DOG=Rex\ The\ Dog
    ENV MY_CAT=fluffy
    
    1
    2
    3

    可以写成:

    ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
        MY_CAT=fluffy
    
    1
    2

    ENV 当容器从生成的映像运行时,使用设置的环境变量将持续存在。您可以使用来查看值 docker inspect,并使用 更改它们 docker run --env <key>=<value>

  • ENV 指令 不允许 <key> <value> ...一次设置多个变量,例如:

    ENV ONE TWO= THREE=world
    
    1

例子 1:创建一个变量,代表 /data/kele 目录

  • Dockerfile 文件添加内容







    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH
    
    1
    2
    3
    4
    5
  • 验证结果:











     

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# pwd
    
    # 返回结果
    /data/kele
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

# ADD指令

笔记

使用该指令前,要明白,该指令只针对 Dockerfile 文件所在的目录下,也称为 上下文的源目录

2021-11-22 @Young Kbt

用来从 context 上下文复制新文件、目录或远程文件 url,并将它们添加到位于指定路径的映像文件系统中。

两种形式:

# Linux 下可选择用户和组
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
1
2
3

包含空格的路径需要后一种形式。

笔记

--chown 功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器。

2021-11-22 @Young Kbt

<src> 可以指定多个资源,但如果它们是文件或目录,则它们的路径被解释为相对于构建上下文的源(即 Dockerfile 所在的目录下)。

每个都 <src> 可能包含通配符,匹配将使用 Go 的 filepath.Match (opens new window) 规则完成。也有很多规则。例如:

  • 添加所有以 "hom" 开头的文件

    ADD home* /mydir/  # 通配符添加多个文件
    
    1
  • ? 可以被替换为任何单个字符

    ADD hom?.txt /mydir/  # 通配符添加
    
    1
  • <dest> 是一个绝对路径,或相对于一个路径 WORKDIR,到其中的源将在目标容器内进行复制。

    ADD test.txt relativeDir/  # 可以指定相对路径
    
    1
  • 使用绝对路径,并将 "test.txt" 添加到 /absoluteDir/

    ADD test.txt /absolutionDir/  # 可以指定绝对路径
    
    1
  • 复制文件的时候,<dest>没有出现一个以上的 /,则是对该文件重命名

    ADD demo-0.0.1-SNAPSHOT.jar app.jar
    
    1
  • 添加包含特殊字符(如[])的文件或目录时,您需要按照 Golang 规则对这些路径进行转义,以防止它们被视为匹配模式。例如,要添加名为 的文件arr[0].txt,请使用以下命令:

    ADD arr[[]0].txt /mydir/
    
    1
  • 如果 <src> 是 URL 并且 <dest> 不以斜杠结尾,则从 URL 下载文件并将其复制到 <dest>

    ADD url <dest>  # 远程文件 url 拉取
    
    1

    如果 <src> 是 URL 并且 <dest> 确实以斜杠结尾,则从 URL 推断文件名并将文件下载到 <dest>/<filename>. 例如,ADD http://example.com/foobar/ 将创建文件 /foobar。URL 必须有一个重要的路径,以便在这种情况下可以发现适当的文件名(http://example.com 将不起作用)。

  • 如果 <src> 是采用可识别压缩格式(identity、gzip、bzip2 或 xz)的本地 tar 存档,则将其解压缩为目录。来自远程 URL 的资源 不会被 解压缩

    ADD xxx.tar <dest>
    
    1

    文件是否被识别为可识别的压缩格式完全取决于文件的内容,而不是文件的名称。例如,如果一个空文件恰好以 .tar.gz 结尾,则不会将其识别为压缩文件,也不会 生成任何类型的解压缩错误消息,而只会将该文件复制到目的地。

  • 如果 <src> 是目录,则复制目录的全部内容,包括文件系统元数据,但是不会复制目录本身。

    ADD /opt /mydir/
    
    1

其他规则:

  • 如果 <src> 是任何其他类型的文件,则将其与其元数据一起单独复制。在这种情况下,如果 <dest> 以斜杠结尾/,它将被视为一个目录,其内容 <src> 将被写入 <dest>/base(<src>)
  • 如果 <src> 直接指定了多个资源,或者由于使用了通配符,则 <dest> 必须是目录,并且必须以斜杠结尾 /
  • 如果 <dest> 不以斜杠结尾,则将其视为常规文件,并将其内容 <src> 写入 <dest>.
  • 如果 <dest> 不存在,则创建它及其路径中所有丢失的目录。

只能在 Dockerfile 所处的目录内进行复制,也就是说 ADD /opt/test.txt /data/kiele 的 test.txt 文件在 Dockerfile 所处路径的 opt 目录里,并不是相对于操作系统的目录而言。

例子 1:添加一个文件到容器里的 /data/kele 目录下

  • 首先在 Dockerfile 目录里创建一个文件 aa.txt

     # 进入 Dockerfile 目录里
     cd /opt/docker-file
     
     # 添加 aa.txt 文件
     touch aa.txt
    
    1
    2
    3
    4
    5
  • Dockerfile 文件添加内容











    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH
    
    ADD aa.txt /data/kele
    # 或者
    # RUN mv a.txt /data/kele
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 验证结果











     

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# ls
    
    # 返回结果
    aa.txt
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

例子 2:添加一个压缩包到容器里的 /data/kele 目录下

  • 首先创建一个压缩包








     

     # 进入 Dockerfile 目录里
     cd /opt/docker-file
     
     # 添加 aa.txt 文件
     touch bb.txt
     
     # 创建压缩包
     tar cvf bb.txt.tar bb.txt
    
    1
    2
    3
    4
    5
    6
    7
    8
  • Dockerfile 文件添加内容









    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH
    
    ADD bb.txt.tar /datakele
    
    1
    2
    3
    4
    5
    6
    7
  • 验证结果:











     

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# ls
    
    # 返回结果
    bb.txt
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

# COPY指令

用来将 context 目录中指定文件复制到镜像的指定目录中。

有两种形式:

# Linux 下选择用户和组
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
1
2
3

笔记

--chown 功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器。

2021-11-22 @Young Kbt

它是 ADD 的缩小版,功能不那么冗余,有一个区别就是:不支持解压

例子 1:添加一个压缩包到容器里的 /data/kele 目录下

  • Dockerfile 文件添加内容











    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH
    
    ADD aa.txt /data/kele
    
    COPY bb.txt.tar /data/kele
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 验证结果











     

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 kele]# ls
    
    # 返回结果
    aa.txt bb.txt.tar
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    说明复制的压缩包不会自动解压。

# VOLUME指令

VOLUME 指令创建一个具有指定名称的挂载点,并将其标记为保存来自本机主机或其他容器的外部挂载卷。该值可以是 JSON 数组、VOLUME ["/var/log/"] 或带有多个参数的纯字符串,例如 VOLUME /var/logVOLUME /var/log /var/db

语法:

VOLUME <容器内>

# 例子:
VOLUME /myvol/greeting
# 或者
VOLUME ["myvol", "greeting"]
1
2
3
4
5
6

缺点:主机目录(挂载点)本质上是依赖于主机的。这是为了保持图像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。因此,您无法从 Dockerfile 中挂载主机目录。该 VOLUME 指令不支持指定 host-dir 参数。您必须在创建或运行容器时指定挂载点。

也就是说,VOLUME 指令使用的是匿名目录挂载,只能指定容器挂载目录,而在宿主机的挂载目录名是一大长串 ID。

例子 1:挂载 /myvol 目录

  • Dockerfile 文件添加内容:











    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 验证结果:










     




     

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像并进入
    docker run -it mycentos7:1.0
    
    # 查看当前的工作目录
    [root@8da133bb7ac4 myvol]# ls
    # 返回结果
    greeting
    
    # 查看文件
    vim greeting
    # 返回结果
    "hello docker"
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    查看宿主机的挂载目录在哪里?




     

    find / -name greeting
    
    # 返回结果
    var/lib/docker/volumes/a9ab2b428361cbdc60beafadb585fb7312bec2fbcb9971c1806a7f0a60ef4de3/_data/greeting
    
    1
    2
    3
    4

# ENTRYPOINT和CMD指令

两个命令都是指定一个容器启动时要运行的命令。

  • ENTRYPOINT 指令,往往用于设置容器启动后的 第一个命令,这对一个容器来说往往是固定的

    docker run 的参数会被当做参数传递给 ENTRYPOINT,形成新的命令组合。

  • CMD 指令,往往用于设置容器启动的第一个命令的 默认参数,这对一个容器来说可以是变化的

    可以有多个 CMD 指令,但只有最后一个生效。

ENTRYPOINT 和 CMD 的两种形式:

  • JSON 数组形式,命令组合时必须用的形式:

    ENTRYPOINT ["executable", "param1", "param2"]
    CMD ["executable", "param1", "param2"]
    
    1
    2
  • 标准格式:

    ENTRYPOINT command param1 param2
    CMD command param1 param2
    
    1
    2

ENTRYPOINT 的最大价值是和 CMD 组合使用,实现传参效果,下面我们开始举三个例子,第一、二例子分别使用两个指令,第三个例子是指令组合使用。

例子 1:使用 CMD 命令

  • Dockerfile 文件添加内容:











     

    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    CMD ls /data/kele
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 验证结果:








     

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像
    docker run -it mycentos7:1.0
    
    # 返回结果
    aa.txt	bb.txt	myvol
    
    1
    2
    3
    4
    5
    6
    7
    8

相比较 ENTRYPOINT,CMD 有一个很大的区别:可以在 docker run 的后面使用参数覆盖掉 CMD 指令

  • Dockerfile 文件添加内容:

    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    CMD ls /data/kele
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 验证结果:





     




    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像
    docker run -it mycentos7:1.0 ls /data
    
    # 返回结果
    kele
    
    1
    2
    3
    4
    5
    6
    7
    8

    第 5 行代码,在启动的时候使用 ls /data 覆盖了 Dockerfile 文件的 ls /data/kele 命令,如果启动的时候使用 pwd 也会覆盖掉 ls /data/kele


     




    # 启动镜像并进入
    docker run -it mycentos7:1.0 pwd
    
    # 返回结果
    /data/kele/myvol
    
    1
    2
    3
    4
    5

    证明了 CMD 是可以被外界命令覆盖的。

例子 2:使用 ENTRYPOINT 命令

  • Dockerfile 文件添加内容:











     

    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    ENTRYPOINT ls /data/kele
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 验证结果:








     

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    # 启动镜像
    docker run -it mycentos7:1.0
    
    # 返回结果
    aa.txt	bb.txt	myvol
    
    1
    2
    3
    4
    5
    6
    7
    8

ENTRYPOINT 指令可以被覆盖吗?

是可以的。它和 CMD 的区别就在于不能被 直接覆盖,但是在 docker run 后使用 --entrypoint 就可以实现覆盖。

使用 pwd 覆盖 Dockerfile 文件的指令:


 




# # 启动镜像
docker run -it --entrypoint pwd mycentos7:1.0

# 返回结果
/data/kele/myvol
1
2
3
4
5

但是 --entrypoint 放在镜像名后面是不行的:


 




# # 启动镜像
docker run -it mycentos7:1.0 --entrypoint pwd

# 返回结果
aa.txt	bb.txt	myvol
1
2
3
4
5

例子 3:组合指令使用

ENTRYPOINT 的指令无法被直接覆盖,可以作为 默认指令,CMD 的指令可以被直接覆盖,可以作为 默认参数

组合指令需要用到的的形式必须是 JSON 数组形式:

ENTRYPOINT ["executable", "param1", "param2"]
CMD ["executable", "param1", "param2"]
1
2
  • Dockerfile 文件添加内容:











     
     

    FROM centos:7
    
    ENV BASH_PATH /data/kele
    
    WORKDIR $BASH_PATH/myvol
    
    RUN echo "hello docker" > $BASH_PATH/myvol/greeting
    
    VOLUME $BASH_PATH/myvol
    
    ENTRYPOINT ["ls"]
    CMD ["/data/kele"]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  • 打包镜像

    # 打包成镜像
    docker build -t mycentos7:1.0 .
    
    1
    2
  • 不传参启动





     

    # 启动镜像
    docker run -it mycentos7:1.0
    
    # 返回结果
    aa.txt	bb.txt	myvol
    
    1
    2
    3
    4
    5
  • 传参启动


     




    # 带参数启动
    docker run -it mycentos7:1.0 /data
    
    # 返回结果
    kele
    
    1
    2
    3
    4
    5

    说明 /data 已经覆盖了 CMD 的默认值 /data/kele实现了传参功能。

    但是因为默认指令是 ls,如果想要其他指令,要么使用 --entrypoint 进行覆盖,要么修改 Dockerfile 文件的 ENTRYPOINT 指令。

启动 Java 的 jar 包可以写出这样:

FROM centos:7

......

ENTRYPOINT ["java","-jar"]
CMD ["默认 jar 包"]
1
2
3
4
5
6

先指定一个默认 jar 包。如果后期修改了 jar 包,直接在 docker run 后面加入新的 jar 包名即可。

# ARG指令

ENTRYPOINT 和 CMD 组合指令只能在 docker run 的时候传参。那有没有想过,想在 docker build 的时候进行传参,把参数传入到 Dockerfile 的某个变量里,这样打包出来的镜像塑造性高。

ARG 指令可以实现这个功能。

ARG <name>[=<default value>]
1

ARG 指令定义了一个变量,用户可以在构建时 docker build 通过使用 --build-arg <varname>=<value> 标志的命令将其传递给构建器。如果用户指定了未在 Dockerfile 中定义的构建参数,则构建会输出警告。

# 输出警告
[Warning] One Or more build-args [foo] were not consumed
1
2

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

FROM centos:7
ARG user1
ARG buildno
# ......
1
2
3
4

警告

不建议使用构建时变量来传递秘密,如 github 密钥、用户凭据等信息,因为可以通过 docker history 等查看历史记录。

2021-11-22 @Young Kbt

# 默认值

一条 ARG 指令可以选择包含一个默认值:

FROM centos:7
ARG user1=someuser
ARG buildno=1
# ......
1
2
3
4

ARG 变量定义从 Dockerfile 中定义它的行开始生效,而不是从命令行或其他地方使用参数开始生效。例如,考虑这个 Dockerfile:

FROM centos:7
WORKDIR ${path:/data}
ARG path
WORKDIR $path
# ......
1
2
3
4
5

通过调用构建此文件传参:

docker build --build-arg path=/data/kele ./
1

第 2 行的工作目录使用了 path 变量,并指定初始值为 /data,但是该变量在随后的第 3 行中才开始定义。所以第 4 行的 path 不是 /data,而是构建时传进来的 /data/kele。在ARG指令定义变量之前,任何变量的使用都会导致空字符串。

ARG 指令在构建阶段(build)结束后失效。要在多个阶段中使用 arg,每个阶段必须包含 arg 指令。

FROM centos:7
ARG SETTINGS
RUN ./run/setup $SETTINGS

# 必须重新定义 SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS
1
2
3
4
5
6
7
8

# 使用ARG变量

其实看到这,很多人都会想到 ARG 不就是 ENV 吗,都是定义一个变量,然而官方不可能这么做,肯定有所区别,那么接下来请仔细阅读下面内容。

使用 ENV 指令定义的环境变量 总是覆盖 ARG 同名指令

FROM centos:7
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER
1
2
3
4

在构建时,传入参数:

 docker build --build-arg CONT_IMG_VER=v2.0.1 .
1

在这种情况下,RUN 指令使用 v1.0.0 而不是 ARG 传递的参数:v2.0.1,此行为类似于 shell 脚本,本地范围的变量覆盖参数传递或环境继承的变量。

如果定义了 ARG 变量,但是又不想在构建阶段传参,可以使用 ENV 进行配合:

FROM centos:7
ARG CONT_IMG_VER
ENV CONT_IMG_VER=${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
1
2
3
4

ARG 指令不同,ENV 值始终保留在构建的映像中,所以 CONT_IMG_VER 仍保留在映像中,CONT_IMG_VER 的默认值是 v1.0.0,哪怕构建阶段不传参数,也不会报错。

# 预定义的ARG

Docker 有一组预定义的 ARG 变量,您可以 ARG 在 Dockerfile 中没有相应指令的情况下使用这些变量。

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy

要使用这些,请使用 --build-arg 标志在命令行上传递它们,例如:

docker build --build-arg HTTPS_PROXY=https://my-proxy.example.com .
1

默认情况下,这些预定义变量的值存入 docker history. 所以尽量不要使用这些变量存入敏感的身份验证信息等。

例如,考虑使用以下 Dockerfile 构建 --build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com

FROM centos:7
RUN echo "Hello World"
1
2

在这种情况下,因为文件里没有 HTTP_PROXY 变量,所以 docker history 不会缓存该变量的信息。

# ARG和ENV区别

  • 在 Dockerfile 中使用,仅在 build docker image 的过程中(包括 CMD 和 ENTRYPOINT)有效,在 image 被创建和 container 启动之后,无效。如果你在 Dockerfile 中使用了 ARG 但并未给定初始值,则在运行 docker build(编译)的时候未指定该 ARG 变量,则会失败。虽然其在 container 启动后不再生效,但是使用 docker history 可以查看到。所以,敏感数据不建议使用 ARG.

# .dockerignore文件

在 docker CLI 将上下文发送到 docker 守护进程之前,它会在上下文的根目录中查找名为 .dockerginore 的文件。如果此文件存在,CLI 将修改上下文以排除与其中模式匹配的文件和目录。这有助于避免不必要地将大型或敏感文件和目录发送到守护进程,并可能使用 ADDCOPY 将它们添加到图像中。

CLI 将 .dockerignore 文件解释为以换行符分隔的模式列表,类似于 Unix shell 的文件 glob。出于匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式 /foo/barfoo/bar 都排除 PATH 的 foo 子目录或位于 URL 的 git 存储库根目录中名为 bar 的文件或目录。两者都不排除任何其他因素。

如果 .dockerignore 文件中的一行在第 1 列中以 # 开头,则该行被视为注释,并在被 CLI 解析之前被忽略。

这是一个示例 .dockerignore 文件:

# comment
*/temp*
*/*/temp*
temp?
1
2
3
4

此文件会导致以下构建行为:

规则 行为
# comment 忽略。
*/temp* 排除根目录的任何子目录中名称以 temp 开头的文件和目录。例如,纯文件 /somedir/temporary.txt 被排除在外,目录 /somedir/temp 也被排除在外
*/*/temp* 从根目录下两级的任何子目录中排除以 temp 开头的文件和目录。例如,不包括 /somedir/subdir/temporary.txt
temp? 排除根目录中名称为 temp 的单字符扩展名的文件和目录。例如,不包括/tempa/tempb

使用 Go 的 filepath.Match 规则完成匹配。预处理步骤将删除前导和尾随空格并消除。预处理后为空的行将被忽略。

除了 Go 的 filepath.Match 规则之外,Docker 还支持一个特殊的通配符字符串 ** 来匹配任意数量的目录(包括零)。例如,**/*.go 将排除(包括生成上下文的根目录)中以 .go 结尾的所有文件

!(感叹号)开头的行可用于排除例外。以下是 .dockerignore 使用此机制的示例文件:

*.md
!README.md
1
2

除 README.md 之外的所有标记文件都从上下文中排除

!异常规则的位置会影响行为:.dockerignore 匹配特定文件的最后一行决定是包含还是排除它。考虑以下示例:

*.md
!README*.md
README-secret.md
1
2
3

除了 README 文件之外,上下文中不包含任何文件 README-secret.md

现在考虑这个例子:

*.md
README-secret.md
!README*.md
1
2
3

包含所有 README 文件。中间的线没有效果,因为 !README*.mdREADME-secret.md 匹配,并且排在最后。

您甚至可以使用该 .dockerignore 文件来排除 Dockerfile.dockerignore 文件。这些文件仍然被发送到守护进程,因为它需要它们来完成它的工作。但是 ADDCOPY 不会将它们复制到镜像中。

最后,您可能希望指定要在上下文中包含哪些文件,而不是要排除哪些文件。为此,请指定 * 为第一个模式,然后是一个或多个 ! 异常模式。

# 指令实战

# CentOS安装指令

官方默认的 CentOS 的情况不支持 vimifconfig 指令

我们自己构建一个支持这些指令的镜像

1、编写文件

FROM centos:7

ENV PATH /usr/local
WORKDIR $MYPATH

RUN yum -y install vim
RUN yum -y install net-tools
1
2
3
4
5
6
7

2、构建镜像

命令最后有一个 . 表示当前目录

docker build -t mycentos7:1.0 .
1

3、运行镜像

docker run -it mycentos7:1.0
1

4、进入容器使用指令

# 使用 vim 指令
[root@438f1c67afb1 /]# vim aa.txt

# 直接创建 aa.txt 文件并进入

# 使用 ifconfig
ifconfig
1
2
3
4
5
6
7

测试后,可以看到,我们自己的新镜像已经支持 vim 和 ifconfig的命令了。

列出镜像的变更历史指令:docker history <镜像名 | 镜像id>

docker history <镜像名 | 镜像id>
1

# CentOS安装Tomcat

先准备好 jdk 和 Tomcat,这里是 jdk-8u11apache-tomcat-9.0.46

1、编写文件

Dockerfile 文件内容:

FROM centos:7

# 把 java 与 tomcat 添加到容器中
ADD jdk-8u11-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.46.tar.gz /usr/local/

# 安装 vim 编辑器
RUN yum -y install vim

# 变量
ENV BASE_PATH /usr/local

# 设置工作目录,即进入容器后默认所在目录
WORKDIR $BASE_PATH

# 配置 Java 与 tomcat 的环境变量
ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.46
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.46
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

# 容器运行时监听的端口
EXPOSE 8080

# 启动时运行 tomcat
# ENTRYPOINT ["/usr/local/apache-tomcat-9.0.46/bin/startup.sh" ]
# CMD ["/usr/local/apache-tomcat-9.0.46/bin/catalina.sh","run"]
# 或者
CMD /usr/local/apache-tomcat-9.0.46/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.46/bin/logs/catalina.out
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

2、构建镜像

docker build -t mytomcat:1.0 .
1

3、运行镜像

docker run -d -p 8080:8080 --name mytomcat -v tomcat_test:/usr/local/apache-tomcat-9.0.46/webapps/test -v tomcat_logs:/usr/local/apache-tomcat-9.0.46/logs --privileged=true mytomcat:1.0
1

如果出现:cannot open directory Permission denied

在挂载目录后面加上 --privileged=true 参数即可。

4、测试

直接在宿主机的 tomcat_test 目录新建一个 test.jsp 文件

cd /var/lib/docker/volumes/tomcat_test/_data

vim test.jsp
1
2
3

随便加入内容:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <title>Insert title here</title>
        </head>
        <body>
            <h2>Hello Docker!</h2>
        </body>
    </html>
1
2
3
4
5
6
7
8
9
10
11
12

打开浏览器访问:http://192.168.199.27:8080/test/test.jsp

IP 地址根据自己的情况填写。

# SpingBoot

1、使用 IDEA 构建一个 SpringBoot 项目

2、编写 Controller

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
	    return "Hello Docker";
    }
}
1
2
3
4
5
6
7

利用 Maven 打成 jar 包。

3、编写文件

将打包好的 jar 包拷贝到 Dockerfile 同级目录,然后开始编写 Dockerfile 文件

FROM java:8

# 拷贝 jar 包到镜像里,并改名为 app.jar
COPY *.jar /app.jar

# 启动镜像执行的命令
CMD ["--server.port=8080"]

# 指定容器内要暴露的端口
EXPOSE 8080

# 启动镜像后,启动 jar 包
ENTRYPOINT ["java","-jar"]
CMD ["/app.jar"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

4、构建镜像

# 构建镜像
docker build -t test-jar:1.0 .

# 运行
docker run -d -P --name test-jar test-jar:1.0
1
2
3
4
5

5、测试

打开浏览器访问:http://192.168.199.27:8080/hello

IP 地址根据自己的情况填写。

更新时间: 2024/01/17, 05:48:13
最近更新
01
JVM调优
12-10
02
jenkins
12-10
03
Arthas
12-10
更多文章>