基本结构

  • 组成

    • 基础镜像信息
    • 维护者信息
    • 镜像操作指令
    • 容器启动时执行的指令
  • Dockerfile reference | Docker Documentation

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #Nginx dockerfile
    #Base images
    FROM centos
    #MAINTAINER 维护人
    MAINTAINER fulsun
    #ADD 添加本地文件到镜像
    ADD pcre-8.37.tar.gz /usr/local/src
    ADD nginx-1.9.3.tar.gz /usr/local/src
    #RUN 在镜像中执行命令
    RUN yum install -y wget gcc_c++ make openssl-devel
    RUN useradd -s /sbin/nologin -M www
    #WORKDIR 镜像中切换到目录
    WORKDIR /usr/local/src/nginx-1.9.3
    RUN yum install -y gcc gcc-c++
    RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-http_stub_status_module --with-pcre=/usr/local/src/pcre-8.37 && make && make install
    RUN echo "daemon off;">>/usr/local/nginx/conf/nginx.conf
    #配置环境变量
    ENV PATH /usr/local/nginx/sbin:$PATH
    EXPOSE 80
    #命令
    CMD ["nginx"]

配置命令

dockerfile指令包括配置指令(配置镜像信息)和操作指令(具体执行操作)两部分

配置命令

指令 说明 格式
ARG 定义创建镜像过程中使用的变量 ARG [=]
FROM 指定所创建的基础镜像 FROM : FROM :
LABEL 为生成的镜像添加元数据标签信息 LABEL = = =
EXPOSE 声明镜像内服务监听的端口 EXPOSE [/…]
ENV 指定环境变量 ENV ENV =
ENTRYPOINT 指定镜像的默认入口命令 ENTRYOINT [“executable”,”param1”,”param2”]:exec调用执行 ENTRYOINT command param1 param2:shell中执行
VOLUME 创建一个数据卷挂载点 VOLUME [“/data”]
USER 指定运行容器时的用户名或UID USER daemon
WORKDIR 配置工作目录 WORKDIR /path
ONBUILD 创建子镜像时指定自动执行的操作命令 ONBUILD [INSTRUCTION]
STOPSIGNAL 指定退出的信号值 STOPSIGNAL signal
HEALTHCHECK 配置所启动容器如何进行健康检查 HEALTHCHECK [OPTIONS] CMD command: 根据所执行命令返回值是否为0来判断; HEALTHCHECK NONE: 禁止基础镜像中的健康检查。
SHELL 指定默认shell类型 SHELL [“executable”, “parameters”]

ARG

  • 定义创建镜像过程中使用的变量 ARG <name>[=<default value>]

  • ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。

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

FROM

  • 指定基础镜像 FROM <image>:<tag>

    1
    2
    ARG VERSION=9.3
    FROM debian:${VERSION}
  • FROM必须是第一条指令。如果在一个dockerfile中指定多个镜像时,使用多个FROM指令

  • 如果不以任何镜像为基础,那么写法为:

    1
    FROM  scratch

LABEL

  • 为镜像添加一些元数据(metadata),以键值对的形式 LABEL <key>=<value> <key>=<value> <key>=<value> ...

  • LABEL会继承基础镜像种的LABEL,如遇到key相同,则值覆盖

  • LABEL 值中包含空格,请像在命令行中一样使用引号和反斜杠

    1
    2
    # 比如我们可以添加镜像的作者:
    LABEL author=fulsun
  • 一个Dockerfile种可以有多个LABEL,可以在一行上指定多个标签,也可以多个label, 如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 您可以在一行上指定多个标签
    LABEL multi.label1="value1" multi.label2="value2" other="value3"

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

    # 指定多个LABEL标签
    LABEL "com.example.vendor"="ACME Incorporated"
    LABEL com.example.label-with-value="foo"
    LABEL version="1.0"
    LABEL description="This text illustrates \
    that label-values can span multiple lines."
  • 要查看镜像的标签,请使用 docker image inspect 命令。您可以使用 –format 选项仅显示标签;docker image inspect --format='' imageID

EXPOSE

  • 声明镜像内服务监听的端口,默认协议是 TCP EXPOSE <port> [<port>/<portocol>…]

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

  • 如果 docker run,指定了自动映射 -P,那么会将所有暴露的端口随机映射到宿主机的高阶端口

  • 如果 docker run,指定了 –net=host 宿主机网络模式,容器中 EXPOSE 指令暴露的端口会直接使用宿主机对应的端口,不存在映射关系

  • 如果 EXPOSE 暴露的端口确定要和某个宿主机端口建立映射关系,还是要用到 docker run -p 参数

    1
    2
    3
    # PORT
    EXPOSE 8080
    EXPOSE 22

ENV

  • ENV 指令将环境变量 <key> 设置为值 <value>。这个值将在构建阶段的所有后续指令的环境中,

  • 该值将被解释为其他环境变量,因此如果引号字符没有转义,它们将被删除。

  • 像命令行解析一样,引号和反斜杠可以用于在值中包含空格。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 一次设置一个(后续会删除)
    ENV <key> <value>
    # 一次设置多个
    ENV <key>=<value> ...

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

    ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
    MY_CAT=fluffy
  • 当使用生成的镜像运行容器时,使用 ENV 设置的环境变量将持久存在于容器内。 你可以使用 docker inspect 查看这些值,并使用 docker run --env <key>=<value> 修改它们。

ENTRYPOINT

  • 指定的镜像的默认入口命令, 该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的参数

  • 每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个起效。

  • ENTRYPOINT 有两种格式, exec调用执行需要使用双引号

    1
    2
    ENTRYPOINT ["executable", "param1", "param2"] (exec调用执行,首选)
    ENTRYPOINT command param1 param2 (shell中执行)
  • exec 格式 docker run <image> 后面跟的命令行参数将会添加到 ENTRYPOINT 所有参数的最后,且会覆盖掉所有 CMD 命令中的参数。ENTRYPOINT 命令可以通过 docker run --entrypoint 参数来覆盖 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@node1 docker]# cat Dockerfile
    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]

    [root@node1 docker]# docker build -t top .
    [root@node1 docker]# docker run -it --rm --name test -d top -H
    [root@node1 docker]# docker exec -it test ps aux
    USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
    root 1 0.2 0.0 5960 1708 pts/0 Ss+ 02:33 0:00 top -b -H
    root 7 0.0 0.0 5888 1480 pts/1 Rs+ 02:34 0:00 ps aux

  • shell 格式会忽略所有 CMD 命令的参数和 docker run 的命令行参数,ENTRYPOINT 要运行的命令会作为 /bin/sh -c 的子命令运行,而且 /bin/sh 不会传递信号,也就是说 ENTRYPOINT 要运行的命令不是 PID 为 1 的进程,且不会收到 Unix 信号,所以你要执行的命令不会收到 docker stop <container> 发出的 SIGTERM 信号。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [root@node1 docker]# cat Dockerfile2
    FROM ubuntu
    ENTRYPOINT top -b
    CMD -c

    [root@node1 docker]# docker build -t top2 -f Dockerfile2 .
    [root@node1 docker]# docker run -it --rm --name test2 -d top2 -H
    [root@node1 docker]# docker exec -it test2 ps aux
    USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
    root 1 1.4 0.0 2600 648 pts/0 Ss+ 02:44 0:00 /bin/sh -c top
    root 6 0.0 0.0 5960 1708 pts/0 S+ 02:44 0:00 top -b
    root 7 0.0 0.0 5888 1476 pts/1 Rs+ 02:44 0:00 ps aux


VOLUME

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

    • 避免重要的数据,因容器重启而丢失,这是非常致命的。
    • 避免容器不断变大。
  • 格式:

    1
    2
    3
    4
    VOLUME ["<路径1>", "<路径2>"...]
    VOLUME <路径>

    VOLUME ["/data"]
  • 在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。

USER

  • 当服务不需要管理员权限时,可以通过该命令指定运行用户,并且可以在Dockerfile中创建所需要的用户。指定运行容器时的用户名或UID

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    USER <user>[:<group>]

    USER <UID>[:<GID>]

    USER postgres

    # 其中用户名或ID是指可以在容器基础镜像中找到的用户。
    # 如果在容器基础镜像中没有创建特定用户,则在USER指令之前添加useradd命令以添加特定用户。
    RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
    # 要临时获取管理员权限可以使用gosu命令, gosu是个工具,用来提升指定账号的权限
  • 使用 USER 指定用户时,可以使用用户名、UID 或 GID,或是两者的组合

  • 使用 USER 指定用户后,Dockerfile 中后续的命令 RUN、CMD、ENTRYPOINT 都将使用该用户

  • 也可以使用 docker run -u 指定用户 docker run -i -t -u 1001 busybox sh

  • 审计方式:返回容器用户名或用户ID。 如果为空,则表示容器以root身份运行

    1
    2
    [root@localhost ~]# docker ps |grep top |awk '{print $1}'|xargs -n1 docker inspect --format='{{.Id}}:User={{.Config.User}}'
    4e53c86daf89a1bac0ed178d043663d2af162ca813ff17864ebdb964d8233459:User=

WORKDIR

  • 为后续的RUN、CMD、ENTRYPOINT指令配置工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。

  • docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。

  • 格式: 推荐WORKDIR指令中只使用绝对路径

    1
    2
    WORKDIR <工作目录路径>
    WORKDIR /path/to/workdir
  • 可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如:

    1
    2
    3
    4
    WORKDIR /a
    WORKDIR b
    WORKDIR c
    RUN pwd # 则最终路径为 /a/b/c

ONBUILD

  • 指定当基于所生成镜像创建子镜像时,自动执行的操作指令。

  • 用于延迟构建命令的执行。简单的说,就是 Dockerfile 里用 ONBUILD 指定的命令,在本次构建镜像的过程中不会执行(假设镜像为 test-build)。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ,这时执行新镜像的 Dockerfile 构建时候,会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令。

  • 格式:

    1
    ONBUILD <其它指令>

STOPSIGNAL

  • 指定所创建镜像启动的容器接收退出的信号值

  • 该信号可以是与内核 syscall 表中的位置匹配的有效无符号数字(例如9),也可以是 SIGNAME 格式的信号名称(例如 SIGKILL)。

    1
    STOPSIGNAL  signal

HEALTHCHECK

  • 配置所启动容器如何进行健康检查(如何判断健康与否),自Docker 1.12开始支持。格式有两种:

    1
    2
    HEALTHCHECK [options] CMD command 根据所执行命令返回值是否为0来判断;
    HEALTHCHECK NODE 禁止基础镜像中的健康检查。
  • OPTION支持如下参数:

    • -interval=DURATION (default: 30s): 过多久检查一次;
    • -timeout=DURATION (default: 30s): 每次检查等待结果的超时;
    • -retries=N (default: 3): 如果失败了,重试几次才最终确定失败。
    • --start-period=DURATION (default: 0s: 如果指定这个参数, 则必须大于 0s ;在这个时间段内如果检查失败, 则不会记录失败次数。 如果在启动时间内成功执行了健康检查, 则容器将被视为已经启动, 如果在启动时间内再次出现检查失败, 则会记录失败次数。

SHELL

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

    1
    2
    SHELL ["executable", "parameters"]
    # 默认值为["/bin/sh", "-c"]。

操作命令

指令 说明 格式
RUN 运行指定命令 RUN 或RUN [“executable”, “param1”, “param2”]
CMD 启动容器时指定默认执行的命令 CMD [“executable”, “param1”, “param2”]: 相当于执行executable param1 param2,推荐方式; CMD command param1 param2: 在默认的Shell中执行,提供给需要交互的应用; CMD [“param1”, “param2”]: 提供给ENTRYPOINT的默认参数。
ADD 添加内容到镜像 ADD
COPY 复制内容到镜像 COPY

RUN

  • 运行指定命令。

  • 格式

    1
    2
    3
    4
    5
    6
    # 默认将在shell终端中运行命令,即/bin/sh  -c
    RUN <command>
    # 指令会被解析为JSON数组,因此必须用双引号 使用exec执行,不会启动shell环境。
    RUN ["executable", "param1", "param2"]
    # 例如
    RUN ["/bin/bash", "-c","echo hello"]。
  • 每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像层。当命令较长时可以使用\来换行。

CMD

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

  • 支持三种格式:

    1
    2
    3
    4
    5
    6
    # 相当于执行executable param1 param2,推荐方式;
    CMD ["executable", "param1", "param2"]
    # 在默认的Shell中执行,提供给需要交互的应用;
    CMD command param1 param2
    # 提供给ENTRYPOINT的默认参数。
    CMD ["param1", "param2"]:
  • 每个Dockerfile只能有一条CMD命令。如果指定了多条命令,只有最后一条会被执行。

  • 如果用户启动容器时候手动指定了运行的命令(作为run命令的参数),则会覆盖掉CMD指定的命令。

COPY

  • 复制内容到镜像, 格式为COPY <src> <dest>

  • 复制本地主机的<src>(为Dockerfile所在目录的相对路径,文件或目录)下内容到镜像中的<dest>。目标路径不存在时,会自动创建。

  • 路径同样支持正则格式。

    1
    2
    3
    4
    5
    COPY check* /testdir/           # 拷贝所有 check 开头的文件
    COPY check?.log /testdir/ # ? 是单个字符的占位符,比如匹配文件 check1.log

    # 拷贝到data文件夹
    COPY data.tar ./data
  • ADD 命令可以完成 COPY 命令的所有功能,并且还可以完成两类超酷的功能:

    • 解压压缩文件并把它们添加到镜像中
    • 从 url 拷贝文件到镜像中

ADD

  • 添加内容到镜像 ADD <src> <dest> ,只复制目录中的内容而不包含目录自身。

  • 该命令将复制宿主指定的<src>路径下内容到容器中的<dest>路径下。

  • 其中<src>可以是Dockerfile所在目录的一个相对路径(文件或目录);也可以是一个URL;还可以是一个tar文件(自动解压为目录)

  • <dest>可以是镜像内绝对路径,或者相对于工作目录(WORKDIR)的相对路径。

  • 路径支持正则格式

  • 解压压缩文件并把它们添加到镜像中

    1
    2
    WORKDIR /app
    ADD test.tar.gz .
  • 从 url 拷贝文件到镜像中

    1
    2
    3
    4
    5
    # docker 官方建议我们当需要从远程复制文件时,最好使用 curl 或 wget 命令来代替 ADD 命令。
    # 原因是,当使用 ADD 命令时,会创建更多的镜像层,当然镜像的 size 也会更大(下面的两段代码来自 docker 官方文档):
    ADD http://example.com/big.tar.xz /usr/src/things/
    RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
    RUN make -C /usr/src/things all
  • 如果使用下面的命令,不仅镜像的层数减少,而且镜像中也不包含 big.tar.xz 文件:

    1
    2
    3
    4
    5
    6
    RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

    # 在解压压缩文件并把它们添加到镜像

命令区别

CMD与ENTRYPOINT

  1. dockerfile中的 CMD

    • 每个dockerfile中只能有一个CMD如果有多个那么只执行最后一个。

    • CMD启动docker的时候执行的命令,如果执行docker run命令时添加指令,会覆盖cmd的命令选项,举个简单例子:

      1
      2
      3
      4
      docker run -itd --name test docker_image /bin/bash -c
      # 镜像名称后面跟了一个/bin/bash -c ,其实等价于在dockerfile中的
      CMD ["/bin/bash","-c"]
      # 如果dockerfile中的CMD中有了 CMD["/bin/bash","-c"],那么就不用在执行的时候再添加了,如果添加了参数的话那么就相当于要执行你添加的参数,默认的CMD中的参数就无效了。
  2. dockerfile中的ENTRYPOINT

    • 一个dockerfile中ENTRYPOINT也只能存在一个,若存在多个那么只执行最后一个

    • dockerfile中有ENTRYPOINT ["tail","-f","/usr/local/aaa"]这句,那么你启动的时候镜像就执行了这个里面的内容,如果你像上面带参数的话就相当于在这个执行的内容后面再加入参数

      1
      2
      3
      4
      # 如果我们的dockerfile中有ENTRYPOINT ["tail","-f","/usr/local/aaa"]
      # 启动
      docker:docker run -itd --name test docker_image /bin/bash -c
      # 此时就相当于我们启动docker的时候执行了: tail -f /usr/local/aaa /bin/bash -c
  3. CMD和ENTRYPOINT总结: 一般还是会用entrypoint的中括号形式作为docker 容器启动以后的默认执行命令,里面放的是不变的部分,可变部分比如命令参数可以使用cmd的形式提供默认版本,也就是run里面没有任何参数时使用的默认参数。如果我们想用默认参数,就直接run,否则想用其他参数,就run 里面加参数。

COPY和ADD

  • 唯一区别在于是否支持从远程URL获取资源。

    • COPY指令只能从执行docker build所在的主机上读取资源并复制到镜像中。
    • 而ADD指令还支持通过URL从远程服务器读取资源并复制到镜像中。
  • 满足同等功能的情况下,推荐使用COPY指令。ADD指令更擅长读取本地tar文件并解压缩。

shell和exec格式

  • Shell 格式 <instruction> <command> 例如:

    1
    2
    3
    RUN apt-get install python3
    CMD echo "Hello world"
    ENTRYPOINT echo "Hello world"
  • 当指令执行时,shell 格式底层会调用 /bin/sh -c <command>, 例如下面的 Dockerfile 片段:

    1
    2
    3
    4
    ENV name fulsun
    ENTRYPOINT echo "Hello, $name"

    # 执行 docker run <image> 将输出: Hello,fulsun
  • Exec 格式, <instruction> ["executable", "param1", "param2", ...],例如:

    1
    2
    3
    RUN ["apt-get", "install", "python3"]
    CMD ["/bin/echo", "Hello world"]
    ENTRYPOINT ["/bin/echo", "Hello world"]
  • 当指令执行时,会直接调用 <command>,不会被shell 解析。

    1
    2
    3
    4
    5
    ENV name fulsun

    ENTRYPOINT ["/bin/echo", "Hello, $name"]

    # 运行容器将输出: Hello, $name #注意环境变量“name”没有被替换。
    1
    2
    3
    4
    5
    # 如果希望使用环境变量,照如下修改
    ENV name fulsun
    # -c 命令表示后面的参数将会作为字符串读入作为执行的命令。
    # /bin/bash -c 后面接 命令 ,而 /bin/bash 后面接 执行的脚本。
    ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
  • 使用场景

    • CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可以。
    • 使用 RUN 指令安装应用和软件包,构建镜像。
    • 如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。
    • 如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。

构建镜像

  • 基本格式 docker build [OPTIONS] <PATH | URL | ->

  • 命令选项 docker build | Docker Documentation

    Name, shorthand Default Description
    –add-host 添加自定义主机到IP的映射(host: ip)
    –build-arg 设置构建时变量
    –cache-from 视为缓存源的镜像
    –cgroup-parent 容器可选的父安全组
    –compress 使用gzip压缩构建上下文
    –cpu-period 限制CPU CFS(完全公平调度程序) 期限
    –cpu-quota 限制CPU CFS(完全公平的调度程序)配额
    –cpu-shares,-c CPU份额(相对重量)
    –cpuset-cpus 允许执行的CPU(0-3,0,1)
    –cpuset-mems 允许执行的MEM(0-3,0,1)
    –disable-content-trust 跳过镜像验证
    –file,-f Dockerfile的名称(默认为“ PATH / Dockerfile”)
    –force-rm 始终取出中间容器
    –iidfile 将镜像ID写入文件
    –isolation 集装箱隔离技术
    –label 设置镜像的元数据
    –memory,-m 内存限制
    –memory-swap 交换限制等于内存加交换:”-1”以启用无限交换
    –network 在构建期间为RUN指令设置联网模块 API1.25+
    –no-cache 构建镜像时不要使用缓存
    –output,-o 输出目的地(格式:类型=本地,目的地=路径) API 1.40+
    –platform 如果服务器具有多平台功能,请设置平台 API 1.32+ 实验(守护程序)
    –progress auto 设置进度输出的类型(自动,普通,tty)。使用普通显示容器输出
    –pull 始终尝试提取镜像的较新版本
    –quiet,-q 禁止生产输出并成功打印镜像ID
    –rm true 构建成功后删除中间容器
    –secret 公开文件的秘密文件(仅在启用BuildKit的情况下): id = mysecret,src = / local / secret API 1.39+
    –security-opt 安全选项
    –shm-size /dev/shm的大小
    –ssh SSH代理套接字或用于公开构建的密钥(仅在启用BuildKit的情况下)(格式: default
    –stream 流附加到服务器以协商构建上下文 API 1.31+ 实验性(守护程序)
    –tag,-t 名称以及“ name: tag”格式的标签(可选)
    –target 设置要构建的目标构建阶段。
    –ulimit Ulimit选项

选择父镜像

  • 用户可以选择两种镜像作为父镜像,一种是所谓的基础镜像(baseimage)

  • 另外一种是普通的镜像(往往由第三方创建,基于基础镜像)

  • FROM scratch: 该镜像是一个空的镜像,可以用于构建busybox等超小镜像,可以说是真正的从零开始构建属于自己的镜像。

  • 如何制作大小为0 的镜像

    1
    2
    3
    4
    $ tar cv --files-from /dev/null | docker import - scratch
    $ docker image ls
    REPOSITORY TAG IMAGE ID CREATED SIZE
    scratch latest 775bfce21429 9 minutes ago 0B

dockerignore文件

  • 将不检查的目录,文件写到同Dockerfile目录下的.dockerignore文件中, docker build命令将不再检查在.dockerignore文件中的目录和文件,在创建镜像时候不将无关数据发送到服务端。

  • .dockerignore 文件的写法和 .gitignore 类似,支持正则和通配符,具体规则如下:

    • 每行为一个条目;
    • 空行被忽略;
    • 构建上下文路径为所有文件的根路径;
  • 文件匹配规则具体语法如下:

    1
    2
    3
    4
    5
    #	注释
    * 匹配0或多个非/的字符
    ? 匹配1个非/的字符
    ** 0个或多个目录
    ! 除...外,需结合上下文语义
    1
    2
    3
    # 除 README.md 外,所有其他 md 文件都被 docker 忽略
    *.md
    !README.md

多步骤创建

  • 对于需要编译的应用(如C、Go或Java语言等)来说,通常情况下至少需要准备两个环境的Docker镜像:
    • 编译环境镜像: 包括完整的编译引擎、依赖库等,往往比较庞大。作用是编译应用为二进制文件;
    • 运行环境镜像: 利用编译好的二进制文件,运行应用,由于不需要编译环境,体积比较小。 使用多步骤创建,可以在保证最终生成的运行环境镜像保持精简的情况下,使用单一的Dockerfile,降低维护复杂度。

编写注意事项

  • 精简镜像用途: 尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像;
  • 选用合适的基础镜像: 容器的核心是应用。选择过大的父镜像(如Ubuntu系统镜像)会造成最终生成应用镜像的臃肿,推荐选用瘦身过的应用镜像(如node:slim),或者较为小巧的系统镜像(如alpine、busybox或debian);
  • 提供注释和维护者信息: Dockerfile也是一种代码,需要考虑方便后续的扩展和他人的使用;
  • 正确使用版本号: 使用明确的版本号信息,如1.0,2.0,而非依赖于默认的latest。通过版本号可以避免环境不一致导致的问题;
  • 减少镜像层数: 如果希望所生成镜像的层数尽量少,则要尽量合并RUN、ADD和COPY指令。通常情况下,多个RUN指令可以合并为一条RUN指令;
  • 恰当使用多步骤创建(17.05+版本支持): 通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。当然,用户也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个Dockerfile。
  • 使用.dockerignore文件: 使用它可以标记在执行docker build时忽略的路径和文件,避免发送不必要的数据内容,从而加快整个镜像创建过程。
  • 及时删除临时文件和缓存文件: 特别是在执行apt-get指令后,/var/cache/apt下面会缓存了一些安装包;
  • 提高生成速度: 如合理使用cache,减少内容目录下的文件,或使用dockerignore文件指定等;
  • 调整合理的指令顺序: 在开启cache的情况下,内容不变的指令尽量放在前面,这样可以尽量复用;
  • 减少外部源的干扰: 如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错。

常用dockerfile

centos系统初始化

1
2
3
4
5
FROM centos:latest
RUN rm -rf /etc/yum.repos.d/* && curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo && yum clean all && yum -y install epel-release vim wget lrzsz gcc gcc-c++ net-tools chrony passwd && yum -y update
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
RUN echo "set nu" >> ~/.vimrc && echo "set ts=4" >> ~/.vimrc && cat ~/.vimrc
RUN echo 'alias ls="ls --color"' >> ~/.bashrc
1
2
3
# 使用sleep便于调试
RUN echo -e "#! /bin/bash\nwhile true\ndo\nsleep 1\ndone" > /sleep.sh
CMD ["sh","/sleep.sh"]
1
2
3
4
5
6
7
8
# 开启sshd服务
RUN yum install openssh-server passwd -y
RUN /usr/bin/echo "CuiLiang@0302" | /usr/bin/passwd --stdin root
RUN /usr/bin/sed -i 's/.session.required.pam_loginuid.so./session optional pam_loginuid.so/g' /etc/pam.d/sshd && /bin/sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config && /bin/sed -i "s/#UsePrivilegeSeparation.*/UsePrivilegeSeparation no/g" /etc/ssh/sshd_config
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key && ssh-keygen -t rsa -f /etc/ssh/ssh_host_ecdsa_key && ssh-keygen -t rsa -f /etc/ssh/ssh_host_ed25519_key
EXPOSE 22
RUN echo -e "#! /bin/bash\n/usr/sbin/sshd -D" > /run.sh
CMD ["/usr/sbin/sshd","-D"]
1
2
3
4
# yum安装python38
RUN yum install python38 python38-devel mysql-devel sudo -y
RUN mkdir /root/.pip/
RUN echo -e "[global]\nindex-url = https://pypi.tuna.tsinghua.edu.cn/simple\n[install]\ntrusted-host=mirrors.aliyun.com" > /root/.pip/pip.conf
1
2
3
4
5
6
7
8
9
10
11
# 源码安装python39
RUN yum install -y wget gcc make mysql-devel
WORKDIR /tmp
RUN wget https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz
RUN tar -zxf Python-3.9.0.tgz
RUN /tmp/Python-3.9.0/configure --prefix=/usr/local/python3.9.0 --enable-optimizations --with-ssl
RUN make && make install
RUN echo "export PATH=$PATH:/usr/local/python3.9.0/bin" >> /etc/profile
RUN source /etc/profile
RUN mkdir /root/.pip/
RUN echo -e "[global]\nindex-url = https://pypi.tuna.tsinghua.edu.cn/simple\n[install]\ntrusted-host=mirrors.aliyun.com" > /root/.pip/pip.conf

多个from

Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许一个Dockerfile 中出现多个 FROM 指令。

  • 老版本Docker中为什么不支持多个 FROM 指令

  • Docker镜像本质是由一堆文件组成,最主要的文件是层。

  • Dockerfile 中,大多数指令会生成一个层

  • Docker镜像的每一层只记录文件变更,在容器启动时,Docker会将镜像的各个层进行计算,最后生成一个文件系统,这个被称为 联合挂载。

  • Docker的各个层是有相关性的,在联合挂载的过程中,系统需要知道在什么样的基础上再增加新的文件。那么这就要求一个Docker镜像只能有一个起始层,只能有一个根。所以,Dockerfile中,就只允许一个 FROM指令。因为多个 FROM 指令会造成多根,则是无法实现的。

多FROM的意义

  • 多个 FROM 指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条 FROM 为准,之前的 FROM 会被抛弃,那么之前的FROM 又有什么意义呢?

  • 每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。

  • 最大的使用场景是将编译环境和运行环境分离,比如,之前我们需要构建一个Go语言程序,那么就需要用到go命令等编译环境,我们的Dockerfile可能是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # Go语言环境基础镜像
    FROM golang:1.10.3
    # 将源码拷贝到镜像中
    COPY server.go /build/
    # 指定工作目录
    WORKDIR /build
    # 编译镜像时,运行 go build 编译生成 server 程序
    RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server
    # 指定容器运行时入口程序 server
    ENTRYPOINT ["/build/server"]

  • 基础镜像 golang:1.10.3 是非常庞大的,因为其中包含了所有的Go语言编译工具和库,而运行时候我们仅仅需要编译后的 server 程序就行了,不需要编译时的编译工具,最后生成的大体积镜像就是一种浪费。

  • 在 Docker 17.05版本以后,就有了新的解决方案,直接一个Dockerfile就可以解决:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 编译阶段
    FROM golang:1.10.3
    COPY server.go /build/
    WORKDIR /build
    RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server
    # 运行阶段
    FROM scratch
    # 从编译阶段的中拷贝编译结果到当前镜像中
    COPY --from=0 /build/server /
    ENTRYPOINT ["/server"]
  • 这个 Dockerfile 的玄妙之处就在于 COPY 指令的 --from=0 参数,从前边的阶段中拷贝文件到当前阶段中,多个FROM语句时,0代表第一个阶段。除了使用数字,我们还可以给阶段命名,比如:

    1
    2
    3
    4
    5
    6
    7
    # 编译阶段 命名为 builder
    FROM golang:1.10.3 as builder
    # ... 省略
    # 运行阶段
    FROM scratch
    # 从编译阶段的中拷贝编译结果到当前镜像中
    COPY --from=builder /build/server /
  • 更为强大的是,COPY --from 不但可以从前置阶段中拷贝,还可以直接从一个已经存在的镜像中拷贝。比如,

    1
    2
    FROM ubuntu:16.04
    COPY --from=quay.io/coreos/etcd:v3.3.9 /usr/local/bin/etcd /usr/local/bin/
  • 我们直接将etcd镜像中的程序拷贝到了我们的镜像中,这样,在生成我们的程序镜像时,就不需要源码编译etcd了,直接将官方编译好的程序文件拿过来就行了。

  • 有些程序要么没有apt源,要么apt源中的版本太老,要么干脆只提供源码需要自己编译,使用这些程序时,我们可以方便地使用已经存在的Docker镜像作为我们的基础镜像。但是我们的软件有时候可能需要依赖多个这种文件,我们并不能同时将 nginx 和 etcd 的镜像同时作为我们的基础镜像(不支持多根),这种情况下,使用 COPY --from 就非常方便实用了。