11. Dockerfile
docker build 命令从 Dockerfile
和context 来构建自定义的镜像。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=hello,ghi=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 orcmd /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
的路径是构建目录的相对路径,并且src
和dest
都不能使用 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格式⚓
该格式将防止使用任何CMD
或run
命令行参数,但是有一个缺点:你的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"]
如果需要为单个可执行文件编写启动脚本,则可以使用 exec
和 gosu
命令确保最终可执行文件接收 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='
来指定转义符。