Docker - Dockerfile
# 什么是Dockerfile
Dockerfile 可以认为是 Docker 镜像的描述文件,是由一系列命令和参数构成的脚本。主要作用是用来构建 docker 镜像的构建文件。
更直接的理解:Dockerfile 就是一个文件的名字,如 Java 文件叫 xxx.java;Dockerfile 文件就叫 Dockerfile
# 解析过程
# 基本知识
- 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
- # 表示注释
- 每条指令都会创建一个新的镜像层,并对镜像进行提交
# 大致流程
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
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>
2
3
4
5
6
7
8
常用的是第 5 行的打包方式。
例子 1:直接打包在 /opt 下的 Dockerfile 文件,版本为 1.0,名字叫 kele
docker build -t kele:1.0 /opt
不需要写到 Dockerfile 文件名,它默认会找到这个文件
例子 2:利用
-f
打包在 /opt 下的 Dockerfile 文件,版本为 1.0,名字叫 kele
docker build -f /opt/Dockerfile -t kele:1.0 .
# FROM指令
基于那个镜像进行构建新的镜像,在构建时会自动从 docker hub 拉取 base 镜像或者本地拉去镜像(优先本地)。必须作为 Dockerfile 的第一个指令出现,该指令可以出现多次,但是仍以最后一条 FROM 为准。
语法:
FROM <image> [AS <name>] # 默认版本是 latest
FROM <image>[:<tag>] [AS <name>] # 当版本不写 latest 时,请填写其他版本
FROM <image>[@<digest>] [AS <name>] # 使用摘要
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>
# 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"]
2
3
4
5
6
7
8
9
10
11
12
13
在 shell 形式中,您可以使用 \
(反斜杠)将单个 RUN 指令延续到下一行。例如,考虑以下两行:
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
2
它们一起相当于这一行:
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
要使用除 /bin/sh 之外的不同 shell,请使用传入所需 shell 的 exec 形式。例如:
RUN["/bin/bash", "-c", "echo hello"]
重复 RUN
的指令会进入缓存里,下次再 RUN
该指令,直接去缓存获取结果。可以使用 --no-cache
标志使指令缓存无效,例如
docker build --no-cach <指令>
例子 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
11build 后的镜像如果在本地已经有了,则会覆盖本地的镜像
笔记
第一次执行 RUN 指令,会很慢,但是下一次重复执行该指令,就很快了,因为有缓存。
什么时候又变慢呢?RUN 指令发生了修改,那么缓存里旧的指令就被删除,存入新的指令。
2021-11-22 @Young Kbt
# EXPOSE指令
用来指定构建的镜像在运行为容器时 对外暴露端口。
语法:
EXPOSE 端口号/类型 # 如果没有指定类型,则默认暴露都是 tcp
该 EXPOSE
指令 实际上并未发布端口。它充当构建镜像的人和运行容器的人之间的一种文档,关于打算发布哪些端口。要在运行容器时实际发布端口,请使用 -p
指令在 docker run
时来发布和映射一个或多个端口,或者使用 -P
指令来发布所有暴露的端口并将它们映射到高阶端口。
默认情况下,EXPOSE
指定 TCP。您还可以指定 UDP。
EXPOSE 80/tcp
EXPOSE 80/udp
2
在这种情况下,如果您使用 -P
,则端口将为 TCP 公开一次,为 UDP 公开一次。请记住,-P
在主机上使用临时高阶主机端口,因此 TCP 和 UDP 的端口将不同。
无论 EXPOSE
设置如何,您都可以在运行时使用 -p
标志覆盖它们。例如:
docker run -p 80:80/tcp -p 80:80/udp ...
例子 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 <绝对路径> | <相对路径>
注意:WORKDIR 指令可以在 Dockerfile 中多次使用。如果提供了相对路径,它将相对于前一条 WORKDIR
指令的路径。例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
2
3
4
最终 pwd
命令的输出 Dockerfile
将是 /a/b/c
。
该 WORKDIR
指令可以解析先前使用 ENV
。你只能使用在 Dockerfile
。例如:
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
2
3
最终 pwd
命令的输出 Dockerfile
将是 /path/$DIRNAME
。
例子 1:添加工作目录 /data/kele
Dockerfile 文件添加内容
FROM centos:7 WORKDIR /data WORKDIR kele
1
2
3
4WORKDIR 指定的目录,就是进入容器后所处的目录
# 打包成镜像 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 目录
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
2ENV
当容器从生成的映像运行时,使用设置的环境变量将持续存在。您可以使用来查看值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>"]
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
5Dockerfile 文件添加内容
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
8Dockerfile 文件添加内容
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>"]
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/log
或 VOLUME /var/log /var/db
。
语法:
VOLUME <容器内>
# 例子:
VOLUME /myvol/greeting
# 或者
VOLUME ["myvol", "greeting"]
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
2
3
4
5
但是 --entrypoint
放在镜像名后面是不行的:
# # 启动镜像
docker run -it mycentos7:1.0 --entrypoint pwd
# 返回结果
aa.txt bb.txt myvol
2
3
4
5
例子 3:组合指令使用
ENTRYPOINT 的指令无法被直接覆盖,可以作为 默认指令,CMD 的指令可以被直接覆盖,可以作为 默认参数。
组合指令需要用到的的形式必须是 JSON 数组形式:
ENTRYPOINT ["executable", "param1", "param2"]
CMD ["executable", "param1", "param2"]
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 包"]
2
3
4
5
6
先指定一个默认 jar 包。如果后期修改了 jar 包,直接在 docker run
后面加入新的 jar 包名即可。
# ARG指令
ENTRYPOINT 和 CMD 组合指令只能在 docker run
的时候传参。那有没有想过,想在 docker build
的时候进行传参,把参数传入到 Dockerfile 的某个变量里,这样打包出来的镜像塑造性高。
ARG
指令可以实现这个功能。
ARG <name>[=<default value>]
该 ARG
指令定义了一个变量,用户可以在构建时 docker build
通过使用 --build-arg <varname>=<value>
标志的命令将其传递给构建器。如果用户指定了未在 Dockerfile 中定义的构建参数,则构建会输出警告。
# 输出警告
[Warning] One Or more build-args [foo] were not consumed
2
一个 Dockerfile 可能包含一个或多个 ARG
指令。例如,以下是一个有效的 Dockerfile:
FROM centos:7
ARG user1
ARG buildno
# ......
2
3
4
警告
不建议使用构建时变量来传递秘密,如 github 密钥、用户凭据等信息,因为可以通过 docker history
等查看历史记录。
2021-11-22 @Young Kbt
# 默认值
一条 ARG
指令可以选择包含一个默认值:
FROM centos:7
ARG user1=someuser
ARG buildno=1
# ......
2
3
4
ARG
变量定义从 Dockerfile 中定义它的行开始生效,而不是从命令行或其他地方使用参数开始生效。例如,考虑这个 Dockerfile:
FROM centos:7
WORKDIR ${path:/data}
ARG path
WORKDIR $path
# ......
2
3
4
5
通过调用构建此文件传参:
docker build --build-arg path=/data/kele ./
第 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
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
2
3
4
在构建时,传入参数:
docker build --build-arg CONT_IMG_VER=v2.0.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
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 .
默认情况下,这些预定义变量的值存入 docker history
. 所以尽量不要使用这些变量存入敏感的身份验证信息等。
例如,考虑使用以下 Dockerfile 构建 --build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com
FROM centos:7
RUN echo "Hello World"
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 将修改上下文以排除与其中模式匹配的文件和目录。这有助于避免不必要地将大型或敏感文件和目录发送到守护进程,并可能使用 ADD
或 COPY
将它们添加到图像中。
CLI 将 .dockerignore
文件解释为以换行符分隔的模式列表,类似于 Unix shell 的文件 glob。出于匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式 /foo/bar
和 foo/bar
都排除 PATH 的 foo 子目录或位于 URL 的 git 存储库根目录中名为 bar 的文件或目录。两者都不排除任何其他因素。
如果 .dockerignore
文件中的一行在第 1 列中以 # 开头,则该行被视为注释,并在被 CLI 解析之前被忽略。
这是一个示例 .dockerignore
文件:
# comment
*/temp*
*/*/temp*
temp?
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
2
除 README.md 之外的所有标记文件都从上下文中排除
!
异常规则的位置会影响行为:.dockerignore
匹配特定文件的最后一行决定是包含还是排除它。考虑以下示例:
*.md
!README*.md
README-secret.md
2
3
除了 README 文件之外,上下文中不包含任何文件 README-secret.md
。
现在考虑这个例子:
*.md
README-secret.md
!README*.md
2
3
包含所有 README 文件。中间的线没有效果,因为 !README*.md
与 README-secret.md
匹配,并且排在最后。
您甚至可以使用该 .dockerignore
文件来排除 Dockerfile
和 .dockerignore
文件。这些文件仍然被发送到守护进程,因为它需要它们来完成它的工作。但是 ADD
和 COPY
不会将它们复制到镜像中。
最后,您可能希望指定要在上下文中包含哪些文件,而不是要排除哪些文件。为此,请指定 *
为第一个模式,然后是一个或多个 !
异常模式。
# 指令实战
# CentOS安装指令
官方默认的 CentOS 的情况不支持 vim
和 ifconfig
指令
我们自己构建一个支持这些指令的镜像
1、编写文件
FROM centos:7
ENV PATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
2
3
4
5
6
7
2、构建镜像
命令最后有一个 .
表示当前目录
docker build -t mycentos7:1.0 .
3、运行镜像
docker run -it mycentos7:1.0
4、进入容器使用指令
# 使用 vim 指令
[root@438f1c67afb1 /]# vim aa.txt
# 直接创建 aa.txt 文件并进入
# 使用 ifconfig
ifconfig
2
3
4
5
6
7
测试后,可以看到,我们自己的新镜像已经支持 vim 和 ifconfig的命令了。
列出镜像的变更历史指令:docker history <镜像名 | 镜像id>
docker history <镜像名 | 镜像id>
# CentOS安装Tomcat
先准备好 jdk 和 Tomcat,这里是 jdk-8u11
和 apache-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
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 .
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
如果出现:cannot open directory Permission denied
在挂载目录后面加上 --privileged=true
参数即可。
4、测试
直接在宿主机的 tomcat_test 目录新建一个 test.jsp 文件
cd /var/lib/docker/volumes/tomcat_test/_data
vim test.jsp
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>
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";
}
}
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"]
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
2
3
4
5
5、测试
打开浏览器访问:http://192.168.199.27:8080/hello
IP 地址根据自己的情况填写。