Docker DockerFile
hanpy

Dockerfile 是一个用来构建镜像的文本文件,里面包含了一条条构建镜像所需的指令和说明。

官方文档:Dockerfile reference

指令说明

文件中的每一行都是一个指令,每一个指令都是大写字母(不是强制的),指令是是从往下进行执行的,每一个指令都会创建一个新的镜像层,并提交。

FROM 指定基础镜像

FROM指令用来指定基础镜像,必须的指令,并且是第一条指令

Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

1
FROM nginx:alpine

COPY 复制文件

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

1
COPY my.cnf /etc/mysql/conf.d/my.cnf 

源路径可以是多个,也可以使用通配符来匹配,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等,可以通过 --chown=<user>:<group>--chmod=<perms> 来改变复制到容器之后文件的所属用户和权限

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

1
2
COPY hom* /mydir/
COPY --chown=mysql:mysql my.cnf my.cn # 会复制到WORKDIR指定的目录下面,并且用户和用户组修改为mysql

ADD 更高级的复制文件

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

  1. 源路径可以是一个URL,会尝试下载然后复制到目标位置(权限为0666),修改权限需要再来一层 RUN 指令
  2. 如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径>

RUN 执行命令

RUN 指令是用来执行命令行命令的(构建镜像时运行的指令)

有两种方式

  1. shell格式:就像直接在命令行中输入的命令一样
  2. exec格式:RUN ["可执行文件", "参数1", "参数2"]
1
2
3
4
5
# shell格式
RUN chmod 0444 /etc/mysql/conf.d/my.cnf

# exec格式
RUN ["chmod", "0444", "/etc/mysql/conf.d/my.cnf"]

每个指令都会生成一层,Run指令也是同样,所以在使用的时候最好是通过 && 来连接各个命令,这样可以减少生成的层数。

1
2
3
4
5
6
7
8
9
10
11
12
RUN set -x; 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

拿上面的例子来说,在命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,这样会减小镜像的体积。

CMD 容器启动命令

CMD 指令就是用于指定默认的容器主进程的启动命令的。RUN 指令是在镜像构建的时候执行的,CMD 指令是容器启动的时候(docker run)执行的。

命令格式

  1. shell格式:CMD <命令>
  2. exec格式:CMD ["可执行文件", "参数1", "参数2"]

使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。

1
2
3
4
CMD echo $HOME

# 实际的命令会被包装为 sh -c 的参数的形式进行执行
CMD [ "sh", "-c", "echo $HOME" ]

需要注意的地方

  1. CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。
  2. 如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
1
2
3
4
5
6
7
FROM ubuntu

#1 docker run 覆盖情况
CMD ["sh", "-c", "/bin/bash"]

#2 多个CMD覆盖情况
CMD echo "This is a test."
1
2
3
4
5
# (假设没有第二个CMD指令的情况)这样写就是用cat /etc/os-release 替换了 /bin/bash
docker run -it ubuntu cat /etc/os-release

# 虽然有两个CMD指令,但是第二个会覆盖第一个
docker run -it ubuntu

ENTRYPOINT 入口点

ENTRYPOINT 指令的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,需要通过docker run的参数--entrypoint来指定。
当指定了ENTRYPOINT后,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给ENTRYPOINT指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"

需要注意的地方

  1. docker run -it ubuntu cat /etc/os-release 执行时候执行的命令,也会被当做ENTRYPOINT的参数

    1
    2
    3
    4
    5
    FROM ubuntu:18.04
    RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
    ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
    1
    2
    3
    4
    $ docker build -t myip .

    # -i 会作为ENTRYPOINT指令的参数,这样就能输出header头信息了
    $ docker run myip -i
  2. 如果有多个 ENTRYPOINT 指令最后一条生效

ENV 设置环境变量

ENV 指令用来设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。

格式有两种:

  1. ENV [key] [value]
  2. ENV [key1]=[value1] [key2]=[value2]
1
2
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"

下列指令可以支持环境变量展开: ADD、COPY、ENV、EXPOSE、FROM、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD、RUN。

ARG 构建参数

ARG <参数名>[=<默认值>]

ARG 和 ENV 的效果一样,都是设置环境变量。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。

构建命令 docker build 中可以用 --build-arg <参数名> = <值> 来覆盖。

1
2
3
4
5
6
7
8
9
# 只在 FROM 中生效
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

# 要想在 FROM 之后使用,必须再次指定
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

VOLUME 定义匿名卷

定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。

作用:

  1. 避免重要的数据,因容器重启而丢失,这是非常致命的。
  2. 避免容器不断变大。

EXPOSE 暴露端口

格式为 EXPOSE <端口1> [<端口2>...]

EXPOSE 指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。

作用:

  1. 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射
  2. 运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口

WORKDIR 指定工作目录

格式: WORKDIR <工作目录路径>

使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。

1
2
3
WORKDIR /app

RUN echo "hello" > world.txt

会在/app/word.txt写入内容

USER 指定当前用户

格式:USER <用户名>[:<用户组>]

USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。

注意,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。