Dockerfile指令详解

Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

ARG Dockerfile变量

ARG用于定义创建镜像过程中的变量

ARG <name> [=<default value>]

ARG version=9.3

在执行docker build时也可以通过-build-arg[=]来为变量赋值。但与ENV不同的是,ARG变量不会保存当镜像编译成功后,ENV指定的变量将保留在镜像中

FROM 指定基础镜像

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定 基础镜像,因此一个 DockerfileFROM 是必备的指令,并且必须是第一条指令。

Docker Hub 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 nginxredismongomysqlhttpdphptomcat 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 nodeopenjdkpythonrubygolang 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。

如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntudebiancentosfedoraalpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

FROM <image>[:<tag>] [AS <name>]

LABEL标签

LABEL可以为镜像添加元数据标签,我们可以用来辅助过滤出特定镜像

LABEL <key>=<value> <key>=<value> ...

LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

#查看镜像标签
docker image inspect --format='' <image_name>
#镜像过滤
docker images --filter "label=version=1.0"

RUN 执行命令

RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。

#基本语法
RUN <command>
#Shell形式
RUN command1 && command2
#Exec形式
RUN ["executable", "param1", "param2"]
#复制文件并修改权限
RUN mkdir -p /usr/src/app && \
    cp -r /src/* /usr/src/app/ && \
    chmod -R 755 /usr/src/app
#清理临时文件
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
  • 每个 RUN指令都会创建一个新的镜像层:每个 RUN命令都在前一个层的基础上创建新层,因此应尽量将相关的命令合并到一个 RUN指令中,以减少镜像层数和体积。
  • 使用缓存:Docker会缓存每个 RUN命令的结果。如果Dockerfile未更改,后续构建将使用缓存,提升构建速度。
  • 清理临时文件:在安装软件包后,建议清理不再需要的临时文件,以减少镜像大小。

COPY复制文件

COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。

COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

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

注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。

在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。

ADD 高级的COPY

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

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

如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。

ADD [--chown=<user>:<group>] <源路径>... <目标路径>
ADD [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

EXPOSE镜像内监听端口

EXPOSE声明镜像内服务的监听端口

EXPOSE <port> [<port>/<protocol>]

此命令只是声明,不会自动完成端口映射

FROM nginx:alpine
EXPOSE 80/tcp
#端口映射
docker run -p800:80 nginx:alpine
docker run -p <h_port>:<c_port> <image_name>

ENV环境变量

ENV用于指定环境变量,在镜像生成过程中可以被RUN指令使用,在镜像启动的容器中也会存在。

ENV <key> <value>

ENV APP_VERSION 1.0
ENV APP_HOME=/usr/local/app
ENV PATH $PATH:/usr/localbin

指定的环境变量可以在运行时被覆盖掉:

docker run --env <key>=<value> <image_name>

注意,当一条指令中为多个环境变量赋值并且从环境变量读取时,变量都是先赋值再更新。以下最终结果 :

key1 = value1 key2 = value2

ENV key1=value2
ENV key1=value1 key2=${key1}

ENTRYPOINT

指定镜像的默认入口,入口命令会在启动容器时作为根命令执行。

# exec 格式
ENTRYPOINT ["executable", "param1", "param2"]

# shell 格式
ENTRYPOINT command param1 param2

多个ENTRYOPONT只有最后一个起效,在运行时可以被--entrypoint覆盖

docker run --entrypoint

ENTRYPOINT 和 CMD 联合使用

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令。

CMD 和 ENTRYPOINT 区别

CMD                   # 指定这个容器启动的时候要运行的命令,不可以追加命令
ENTRYPOINT            # 指定这个容器启动的时候要运行的命令,可以追加命令

ENTRYPOINT 的第二个应用场景

  • 启动容器就是启动主进程,但启动主进程前,可能需要一些准备工作,比如 mysql 可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决
  • 还可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 root 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务
  • 这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作,这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 )作为命令,在脚本最后执行
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ]

VOLUME数据挂载点

创建一个数据挂载点

VOLUME ["/data"]

FROM centos:7
VOLUME /var/log
RUN echo '/usr/bin/sleep 315360000' > start.sh && \
    chmod +x start.sh
CMD ["/usr/bin/bash","start.sh"]

运行容器时可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和数据持久化。

注意:

如果 Dockerfile 内指定了 volume,并且 docker run -v 参数指向了和 volume 配置的路径一致时, -v 参数会将宿主机路径下的文件覆盖掉 volume 配置的路径下的文件如果 Dockerfile 内指定了 volume,并且 docker run -v 参数没有指向和 volume 配置的路径一致时,-v 参数会将容器内的文件映射到宿主机上,而 Dockerfile 指定的 volume 仍然被指向 docker 数据存储路径下的 volumes 路径下

USER用户指定

指定运行容器时的用户名或UID,后续的RUN等也会使用指定身份

USER <user>[:<group>] 
#or
USER <UID>[:<GID>]
#创建用户
RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
#gosu获取临时管理员权限
RUN set -eux; \
 apt-get update; \
 apt-get install -y gosu; \
 rm -rf /var/lib/apt/lists/*; \
#-u也可指定
docker run -i -t -u 1001 busybox sh

WORKDIR工作目录

为后续的RUN,CMD等指令配置工作目录

WORKDIR path

如果使用多个WORKDIR,后续命令如果是相对路径,则会基于之前的命令指定路径。

WORKDIR /a
WORKDIR b
WORKDIR c
#最终路径为/a/b/c

ONBUILD子镜像命令

基于当前的Dockerfile所生成镜像创建子镜像时自动执行的命令,不影响父镜像。

ONBUILD [INSTRUCTION]

例如:

#test
FROM ubuntu
MAINTAINER hello
ONBUILD RUN mkdir mydir

利用上面的dockerfile文件构建镜像: docker build -t imagea.
利用im镜像创建容器: docker run --name test1 -it imagea /bin/bash

我们发现test1容器的根目录下并没有mydir目录。说明ONBUILD指令指定的指令并不会在自己的构建中执行。

再编写一个新的Dockerfile文件,内容如下

#test
FROM imagea
MAINTAINER hello1

该构建准备使用的基础镜像是上面构造出的镜像imagea
利用上面的dockerfile文件构建镜像: docker build -t imageb .
利用imagea镜像创建容器: docker run --name test2 -it imageb /bin/bash

我们发现test2容器的根目录下有mydir目录,说明触发器执行了。

STOPSIGNAL退出信号值

STOPSIGNAL指令用于设置容器内进程的停止信号。在容器停止时,Docker会发送该信号给容器内的主进程,以请求它正常终止。如果没有设置STOPSIGNAL指令,Docker默认会使用 SIGTERM 信号发送给容器内的主进程。

STOPSIGNAL signal

HELTHCHECK健康检查

设置容器健康检查

HEALTHCHECK [选项] CMD <命令>设置检查容器健康状况的命令

HEALTHCHECK NONE如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

HEALTHCHECK --interval=<间隔> #默认30s
HEALTHCHECK --timeout=<时长> #检查等待结果超时
HEALTHCHECK --retries=<次数> #连续失败指定次数后,将容器状态视为unhealthy,默认3 次
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

SHELL指定默认shell类型

指定其他命令使用shell时的默认shell

SHELL ["executable", "parameters"]

默认为["/bin/sh","-c"]

对于win系统,shell路径中使用“\”作为分割符,建议在Dockerfile开头添加 #escape='指定转义符

CMD默认执行

CMD指令指定启动容器时默认执行的命令

CMD ["executable","param1","param2"]推荐方式
CMD command param1 param2在默认的 Shell 中执行,提供给需要交互的应用 CMD ["param1","param2"]提供给ENTRYPOINT的默认参数