Skip to content

11. Dockerfile

docker build 命令从 Dockerfilecontext 来构建自定义的镜像。context是指一系列使用 PATH or URL 指定位置的文件。

  • PATH 是本地文件系统的文件夹及子文件夹;
  • URL 是 Git 仓库及子模块。

构建时打标签:

docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

1. 构建优化

从 18.09 版本开始,docker 支持一种后台构建方式,它具有以下优点:

  • 检测并跳过执行未使用的构建阶段
  • 并行构建独立构建阶段
  • 两次构建之间仅增量传输构建上下文中的更改文件
  • 在构建上下文中检测并跳过传输未使用的文件
  • 使用具有许多新功能的外部Dockerfile实现
  • Avoid side-effects with rest of the API (中间 images and containers)
  • 优先考虑构建缓存以进行自动修剪

若要使用此种方式构建镜像,需要设置环境变量: DOCKER_BUILDKIT=1

2. 环境变量替换

环境变量需要使用ENV指令进行声明。变量也能在后续的指令被使用。

${variable_name} 语法支持简单的 bash 替换规则:

  • ${variable:-word}
  • ${variable:+word}

支持使用环境变量的指令:

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD (when combined with one of the supported instructions above)

在一条指令中,环境变量替换将对每个变量使用相同的值。例如:

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

结果是:def=helloghi=bye

3. 基本结构

一般而言, Dockerfile 主体内容分为四部分:基础镜像信息、 维护者信息、 镜像操作指令和容器启动时执行指令。

# escape=\ (backslash) 
# This dockerfile uses the ubuntu:xeniel image 
# VERSION 2 - EDITION 1 
# Author: docker_user 
# Command format: Instruction [arguments / command] ..

# Base image to use, this must be set as the first line 
FROM ubuntu:xeniel 

# Maintainer: docker_user <docker_user at email.com> (@docker_user)

LABEL maintainer docker_user<docker_user@email.com> 

# Commands to update the image 
RUN echo "deb http://archive.ubuntu.com/ubuntu/ xeniel main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx 
RUN  echo "\ ndaernon off;"  >> /etc/nginx/nginx.conf 

# Commands when creating a new container 
CMD /usr/sbin/nginx 

首行可以通过注释来指定解析器命令, 后续通过注释说明镜像的相关信息。 主体部分首先使用FROM指令指明所基于的镜像名称, 接下来一般是使用LABEL指令说明维护者信息。后面则是镜像操作指令,每运行一条RUN指令,镜像添加新的一层, 并提交。 最后是CMD指令, 来指定运行容器时的操作命令。

4. 指令说明

Dockerfile 中指令的一般格式为 INSTRUCTION arguments

4.1 ARG

定义创建镜像过程中使用的变量。

ARG <name>[=<default value>]

docker build时可以通过--build-arg为变量赋值:

docker build --build-arg user=what_user .

当镜像编译成功后, ARG 指定的变量将不再存在 (ENV 指定的变量将在镜像中保留)。

Docker 内置了一些镜像创建变量, 用户可以直接使用而无须声明, 包括(不区分大小写):

  • HTTP_PROXY
  • HTTPS_PROXY
  • FTP_PROXY
  • NO_PROXY

4.2 FROM

指定所创建镜像的基础镜像。

FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

任何 Dockerfile 中第一条指令必须为 FROM 指令。 并且, 如果在同一个 Dockerfile 中创建多个镜像时, 可以使用多个 FROM 指令(每个镜像一次)。

  • ARG 是 Dockerfile 中唯一可以在 FROM 之前的指令。
  • FROM可以在单个 Dockerfile 中多次出现以创建多个映像,或者使用一个构建阶段作为另一个构建阶段的依赖项。只需在每个新的FROM指令之前记下提交输出的最后一个镜像ID。每个FROM指令清除先前指令创建的任何状态。
  • <name>可以在后续的FROM和COPY --from = <name | index>指令中使用,以引用此阶段构建的镜像。

FROM指令支持在第一个FROM之前发生的任何ARG指令声明的变量:

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

在FROM之前声明的ARG在构建阶段之外,因此在FROM之后的任何指令中都不能使用它。要使用在第一个FROM之前声明的ARG的默认值,请使用构建阶段内没有值的ARG指令:

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

4.3 RUN

RUN 指令将在当前映像顶部的新层中执行任何命令,并提交结果,生成的提交映像结果将用于 Dockerfile 中的下一步。

它有两种格式:

  • RUN (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)
  • RUN ["executable", "param1", "param2"] (exec form)

当命令较长时可以使用\来换行。例:

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

要使用/bin/sh以外的其他 shell,请使用 exec 形式传入所需的shell。例如:

# 在这种情况下,是 shell 做了环境变量扩展,而不是docker。
RUN ["/bin/bash", "-c", "echo $HOME"]

exec 形式不会进行正常的 shell 处理,除非你像上面的例子一样指定了使用的 shell 。比如 RUN [ "echo", "$HOME" ] 不会对 $HOME 进行替换。

Note

exec 形式会被解析成 JSON 数组,所以必须使用双引号。而且对于\也要进行转义。例如:RUN ["c:\\windows\\system32\\tasklist.exe"]

4.4 CMD

CMD 的主要目的是为执行的容器提供默认动作

它有三种格式:

  • CMD command param1 param2 (shell form)
  • CMD ["executable","param1","param2"] (exec form, this is the preferred form)
  • CMD ["param1","param2"] (as default parameters to ENTRYPOINT)

一个 Dockerfile 文件只能有一条 CMD 指令,若定义了多条,以最后一条为准。

shell 形式与exec 形式也遵循 RUN 的 shell 形式与 exec 形式指令规则。

如果用户为 docker run 指定了参数,则它们将覆盖 CMD 中指定的默认值。

4.5 LABEL

为生成的镜像添加元数据标签信息, 这些信息可以用来辅助过滤出特定镜像。

LABEL <key>=<value> <key>=<value> <key>=<value> ...
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."

4.6 EXPOSE

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

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

注意该指令只是起到声明作用, 并不会自动完成端口映射。

EXPOSE 80/tcp
EXPOSE 80/udp

4.7 ENV

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

ENV <key> <value>
ENV <key>=<value> ...

例如设置系统环境变量:

ENV LC_ALL=en_US.utf8
ENV LANG=en_US.utf8
# 使用 RUN export LANG=en_US.utf8 设置是没用的

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

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

注意:当一条 ENV 指令中同时为多个环境变量赋值并且值也是从环境变量读取时, 会为变量都赋值后再更新。 如下面的指令, 最终结果为 key1=value1 key2=value2:

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

4.8 COPY

COPY指令从本地文件系统的src复制文件或目录,并将它们添加到容器的文件系统中的dest

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

第二种格式的路径可以包含空格。

4.8.1 路径

Important

src的路径是构建目录的相对路径,并且srcdest都不能使用 shell 的扩展,例如 $HOME

src使用 Go 的通配符匹配规则,例如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

dest是绝对路径,或相对于 WORKDIR 的路径,例如:

# <WORKDIR>/relativeDir/
COPY test.txt relativeDir/
# /absoluteDir/
COPY test.txt /absoluteDir/

当文件或文件夹含有特殊字符时,需要使用Go的规则对其进行转义,比如 arr[0].txt

ADD arr[[]0].txt /mydir/
4.8.2 权限

若要改变复制的文件/夹的权限,必须使用--chown=<user>:<group>选项。

默认值是0:0。user 和 group 都可以是字符串或id,若只写了一个值,那么代表 user 和 group 都是该值。

如果容器的根文件系统不包含/etc/passwd/etc/group文件,并且在--chown中使用了用户名或组名的字符串形式,那么COPY指令会失败。使用 id 的形式不会查询这些文件并且不依赖容器的根文件系统。

4.8.3 规则

COPY 遵守以下规则:

  • src 路径必须位于构建的上下文中,你不能 COPY ../something /something,因为 docker build 的第一步会把 context directory (and subdirectories) 发送到 docker daemon。
  • src是个目录,那么只会把目录下所有的内容复制过去,包括文件系统的元数据;不会复制目录本身。
  • 如果src是任何其他类型的文件,则将其与其元数据一起单独复制。在这种情况下,如果dest以斜杠/结束,则它将被视为目录,并且src的内容将写入dest/base(src)。//TODO 不懂是什么意思
  • dest 用是否以/结尾判断是否是一个目录或文件。
  • dest不存在,那么会自动创建缺少的路径。

4.9 ENTRYPOINT

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

例如,下面这个命令会使用默认内容启动nginx,并监听80端口:

docker run -i -t --rm -p 80:80 nginx

有两种格式:

  • ENTRYPOINT ["executable", "param1", "param2"] (exec格式,首选)
  • ENTRYPOINT command param1 param2 (shell格式)

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

shell 形式与exec 形式也遵循 RUN 的 shell 形式与 exec 形式指令规则。

4.9.1 exec格式

此时,后续配置的 CMD 指令的值将作为根命令的参数; 所有传入值作为该命令的参数,并且会覆盖掉所有使用CMD指令指定的参数。

例如,docker run <image> -d会传递-d参数给ENTRYPOINT。可以使用docker run --entrypoint覆盖ENTRYPOINT指令(不能使用sh -c)。

4.9.2 shell格式

该格式将防止使用任何CMDrun命令行参数,但是有一个缺点:你的ENTRYPOINT指令会被作为/bin/sh -c的子命令启动,不能传递信号(signal)。这意味着可执行程序不会是容器的PID 1并且不能接收到Unix signals,所以你的可执行程序不能接收到从docker stop <container>发出的SIGTERM

4.9.3 exec格式ENTRYPOINT举例

可以使用这种格式设置固定的默认命令和参数,然后使用任一格式的 CMD 设置更改其它可变的默认值。

默认执行的命令是top -b -c

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

此时执行的命令是top -b -H

$ docker run -it --rm --name test top -H
top - 08:25:00 up  7:27,  0 users,  load average: 0.00, 0.01, 0.05
Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   2056668 total,  1616832 used,   439836 free,    99352 buffers
KiB Swap:  1441840 total,        0 used,  1441840 free.  1324440 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top

下面的Dockerfile展示了使用ENTRYPOINT在后台运行Apache:

FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

如果需要为单个可执行文件编写启动脚本,则可以使用 execgosu 命令确保最终可执行文件接收 Unix 信号:

#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

最后,如果您需要在关机时进行一些额外的清理(或与其他容器通信),或者协调多个可执行文件,您可能需要确保ENTRYPOINT脚本接收Unix信号,传递它们,然后做了一些工作:

#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too

# USE the trap if you need to also do manual cleanup after the service is stopped,
#     or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM

# start service in background here
/usr/sbin/apachectl start

echo "[hit enter key to exit] or run 'docker stop <container>'"
read

# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"
4.9.4 shell格式ENTRYPOINT举例

您可以为ENTRYPOINT指定一个纯字符串,它将在/bin/sh -c中执行。此格式将使用shell处理替换shell环境变量,并且会忽视任何CMD指令或docker run命令行参数。

为了确保docker stop能正确地向ENTRYPOINT可执行程序发出信号,要牢记使用exec执行命令

FROM ubuntu
ENTRYPOINT exec top -b

当你运行这个镜像时,可以看到单个PID 进程

$ docker run -it --rm --name test top
Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
Load average: 0.08 0.03 0.05 2/98 6
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     R     3164   0%   0% top -b

注意:如果从基础镜像定义CMD,则设置ENTRYPOINT会将CMD重置为空值。在这种情况下,必须在当前镜像中定义CMD才能获得值。

4.9.5 与 CMD 交互
No ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”, “p1_entry”]
No CMD error, not allowed /bin/sh -c exec_entry p1_entry exec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

4.10 VOLUME

创建数据卷挂载点,并将其标记为从本地主机或其他容器保存外部挂载的卷。json数组。运行容器时可以从本地主机或其他容器挂载数据卷, 一般用来存放数据库和需要保持的数据等。

VOLUME ["/data"]

从Dockerfile中更改卷:如果任何构建步骤在声明后更改卷内的数据,那么这些更改将被丢弃。

4.11 USER

指定运行容器时的用户名或UID,后续的RUN、CMD、 ENTRYPOINT指令也会使用指定的用户身份。

USER <user>[:<group>] or
USER <UID>[:<GID>]

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

4.12 WORKDIR

为后续的 RUN、CMD、 ENTRYPOINT、COPY、 ADD指令配置工作目录。

WORKDIR /path/to/workdir

可以使用多个 WORKDIR 令,后续命令如果参数是相对路径, 会基于之前命令指定的路径 :

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

结果是/a/b/c

只能使用Dockerfile中显式设置的环境变量(即只能使用先前在ENV中定义的环境变量):

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

结果是/path/$DIRNAME

4.13 ONBUILD

指定当基于当前Dockerfile生成的镜像创建子镜像时,首先自动执行的操作指令。

ONBUILD [INSTRUCTION]

例如,使用如下的 Dockerfile 创建父镜像 ParentImag ,指定 ONBUILD指令:

# Dockerfile for Parentimage
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

使用 docker build 命令创建子镜像 childImage 时( FROM Parentimage ),会首先执行 Parentimage 配置的 ONBUILD 指令:

# Dockerfile for Chi ldimage
FROM Parentimage

等价于在 Childimage Dockerfi 中添加了如下指令:

#Automatically run the following when building Ch ldimage
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src

该指令在创建专门用于自动编译、检查等操作的基础镜像时,十分有用。

由于 ONBUILD 指令是隐式执行的,推荐在使用它的镜像标 签中进行标注, 例如 ruby:2.l-onbuild。

4.14 STOPSIGNAL

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

STOPSIGNAL signal

4.15 HEALTHCHECK

配置所启动容器如何进行健康检查(如 判断健康与否)。

有两种格式:

  • HEALTHCHECK [OPTIONS] CMD command (check container health by running a command inside the container)
  • HEALTHCHECK NONE (禁止基础镜像中的健康检查)

OPTIONS的值:

  • --interval=DURATION (default: 30s):距上一个检查完成后多久检查一次。
  • --timeout=DURATION (default: 30s):检查结果的超时时间。
  • --start-period=DURATION (default: 0s):为需要时间引导的容器提供初始化时间。在此期间探测失败将不计入最大重试次数。但是,如果健康检查在开始期间成功,容器被视为已启动,所有连续失败将计入最大重试次数。
  • --retries=N (default: 3):如果失败了,重试几次才最终确定失败。

command可以是shell命令或exec数组。

检查结果:

  • 0: success - the container is healthy and ready for use
  • 1: unhealthy - the container is not working correctly
  • 2: reserved - do not use this exit code。保留字。

4.16 SHELL

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

SHELL ["executable", "parameters"]

linux的默认值是["/bin/sh", "-c"];Windows的默认值是["cmd", "/S", "/C"]

注意:因为 shell 路径中使用了\作为分隔符,所以对于 Windows 系统,建议在 Dockerfile 开头添加# escape='来指定转义符。