4.4. 使用 Dockerfile 构建镜像¶
4.4.1. Dockerfile 文件¶
Dockerfile 是一个文本文件,文件包含了一条条的 指令(Instruction),对应 Docker 的镜像是分层存储的,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
Docker 的指令很简单,下面会介绍常用的指令。
4.4.2. 构建镜像命令¶
有了 Dockerfile 后,使用构建命令 docker build 就可以根据 Dockerfile 构建镜像:
# [OPTIONS] 具体可以使用 docker build --help 来查看
docker build [OPTIONS] PATH
# 使用当前目录的 Dockerfile 创建镜像,镜像名为 test:v1
docker build -t test:v1 .
# 使用 URL github.com/creack/docker-firefox 的 Dockerfile 创建镜像
docker build github.com/creack/docker-firefox
# 通过 -f 指定 Dockerfile 文件的位置
docker build -f Dockerfile路径 -t test:v1 .
如果构建镜像失败,将会在系统中产生无用的虚悬镜像和一个停止状态的容器,需要删除。 删除虚悬镜像详见 虚悬镜像 章节。
4.4.3. Dockerfile 指令¶
4.4.3.1. FROM 指定基础镜像¶
所谓构建镜像,那一定是以一个镜像为基础,在其上进行构建,而 FROM 就是指定 基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且 必须是第一条指令。:
FROM 镜像名
# 例如
FROM ubuntu:18.04
4.4.3.2. RUN 执行命令¶
RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其主要格式为:
RUN shell命令行
# 例如
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
之前说过,Dockerfile 中每一个指令都会建立一层,每一条 RUN 指令,都会在上一层基础上新建立一层,构成新的镜像。
而上面的这种写法,创建了7层镜像,这是完全没有意义的。 因为很多指令都是可以合并的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。
上面的 Dockerfile 正确的写法应该是这样:
RUN buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。
因此,这里没有使用很多个 RUN 对应不同的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来,将之前的7层,简化为了1层。
此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。
这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
4.4.3.3. WORKDIR 指定工作目录¶
在构建镜像时有时需要切换工作目录,例如,想要在 /etc 目录下创建一个文件 test.txt, 如果使用 RUN cd 工作目录 可能会出现问题:
RUN cd /etc
RUN touch test.txt
上面的结果: test.txt 文件会创建在根目录 / 之下,而不是在 /etc 之下。
这是因为之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更生成新镜像。
第1层 RUN cd /etc 的执行仅仅是当前进程的工作目录变更,内存上的变化而已,其结果不会造成任何文件变更。
而到第2层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。
为了解决上述问题,可以将所有需要在变更工作目录后执行的操作,写进一条指令里:
RUN cd /etc && touch test.txt
或者使用 WORKDIR 指令来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录。同时,如该目录不存在,WORKDIR 会帮你建立目录。该指令格式为:
WORKDIR 工作目录路径
# 例如
WORKDIR /etc
RUN touch test.txt
# 之后所有的操作都在 /etc 目录下进行
4.4.3.4. COPY|ADD 复制文件¶
COPY 指令将本地源文件或目录拷贝到新的一层的镜像内的目标路径,比如:
COPY 本地源文件或目录路径 镜像内的目标路径
# 例如
# 拷贝文件
COPY package.json /usr/src/app/
# 拷贝目录
COPY VINS-Mono /root/VINS-Mono
ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能:
- 源文件 可以是一个
URL, 即网上的文件。 - 如果 源文件 为一个
tar压缩文件的话,压缩格式为gzip,bzip2以及xz的情况下,ADD指令将会自动解压缩这个压缩文件到目标路径。
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,
尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。
4.4.3.5. ENV 设置环境变量¶
ENV 指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是容器运行时的应用,都可以直接使用这里定义的环境变量。其格式为:
ENV 环境变量名 value
ENV 环境变量名1=value1 环境变量名2=value2 ...
# 例如
ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES compute,utility,graphics,video
4.4.3.6. ARG 构建参数¶
ARG 和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。其格式为:
ARG 参数名=默认值
#例如
ARG DEBIAN_FRONTEND=noninteractive
Dockerfile 中的 ARG 定义的变量是可以在构建命令 docker build 中用 --build-arg 参数名=值 来覆盖。
4.4.4. 构建镜像时使用代理¶
构建镜像时会需要执行 RUN apt update && apt install .... 等需要联网获取数据的命令,
可以在构建时使用 --network、--build-arg http_proxy、--build-arg https_proxy 和 --build-arg no_proxy 来配置代理:
# --network host 指定使用宿主机的网络
# --build-arg http_proxy 指定 http 代理
# --build-arg https_proxy 指定 https 代理
# --build-arg no_proxy 指定哪些地址不使用代理
docker build --network host \
--build-arg http_proxy=http://127.0.0.1:8118 \
--build-arg https_proxy=http://127.0.0.1:8118 \
--build-arg no_proxy=.tuna.tsinghua.edu.cn \
-t test:latest .
4.4.5. 构建镜像实例¶
构建镜像实例请参照整个 Docker 教程最终的实例:实例-基于容器环境进行开发