Dockerfile 是一个文本格式的配置文件, 用户可以使用 Dockerfile 来快速创建自定义的镜像.
基本结构
Dockerfile 由一行行命令语句组成, 并且支持以 # 开头的注释行.
一般而言, Dockerfile 分为四部分: 基础镜像信息 维护者信息 镜像操作指令和容器启动时执行指令. 例如:
FROM centos:latest
MAINTAINER docker_user docker_user@email.com
RUN yum -y update
RUN yum -y install systemd systemd-libs
RUN yum clean all;
CMD ["/usr/sbin/init"]
其中, 一开始必须指明所基于的镜像名称, 接下来一般是说明维护者信息. 后面则是镜像操作指令, 例如 RUN 指令, RUN 指令将对象执行跟随的命令. 每运行一条 RUN 指令, 镜像就添加新的一层, 并提交. 最后是 CMD 指令, 用来指定运行容器时的操作命令.
命令详解
1.FROM
指定所创建镜像的基础镜像, 如果本地不存在, 则默认会去 Docker Hub 下载指定镜像.
格式为 FROM<image> 或 FROM<image>:<tag> 或 FROM<image>@<digest>
任何 Dockerfile 中的第一条指令必须为 FROM 指令. 并且, 如果在同一个 Dockerfile 中创建多个镜像, 可以使用多个 FROM 指令(每个镜像一次).
2. MAINTAINER
指定维护者信息, 格式为 MAINTAINER<name>
. 例如:
MAINTAINER docker_user@email.com
该信息会写入生成镜像的 Author 属性域中.
3. RUN
运行指定命令.
格式为 RUN<command> 或 RUN ["executable", "param1", "param2"]. 注意, 后一个指令会被解析为 Json 数组, 因此必须用双引号.
前者默认在 shell 终端中运行命令, 即 /bin/sh -c
; 后者则使用 exec
执行, 不会启动 shell 环境.
指定使用其他终端类型可以通过第二种方式实现, 例如 RUN ["/bin/bash", "-c", "echo hello"]
.
每条 RUN 指令将在当前镜像的基础上执行指定命令, 并提交为新的镜像. 当命令较长时可以使用 \ 来换行.例如:
RUN apt-get update \
&& apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev \
&& rm -rf /var/cache/apt
4.CMD
CMD 指令用来指定启动容器时默认执行的命令. 它支持三种格式:
1.CMD ["executable", "param1", "param2"]
使用 exec 执行, 是推荐使用的方式.
2.CMD command param1 param2
在 /bin/sh
中执行, 提供给需要交互的应用.
3.CMD ["param1", "param2"]
提供给 ENTRYPOINT 的默认参数.
每个 Dockerfile 只能一个 CMD 命令. 如果指定了多条命令, 只有最后一条会被执行.
如果用户启动容器时, 手动指定了运行的命令(作为 run 的参数), 则会覆盖掉 CMD 指定的命令.
5.LABEL
LABEL 指令用来指定生成镜像的元数据标签信息.
格式为 LABEL <key>=<value> <key>=<value> <key>=<value>...
例如:
LABEL version="1.0"
LABEL description=""
6.EXPOSE
声明镜像内服务所监听的端口.
格式为EXPOSE <port> [<port>...]
EXPOSE 22 80 8080
注意, 该指令只是起到声明作用, 并不会自动完成端口映射.
在启动容器时需要使用 -P
, Docker 主机会自动分配一个宿主机的临时端口转发到指定的端口; 使用 -p
, 则可以具体指定哪个宿主机的本地端口会映射过来.
7.ENV
指定环境变量, 在镜像生成过程中会被后续 RUN 指令使用, 在镜像启动的容器中也会存在.
格式为: ENV<key><value> 或 <key>=<value>....
例如:
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN .....
ENV ....
指令指定的环境变量在运行时可以被覆盖掉, 如 docker run --env <key>=<value> built_image
.
8.ADD
该指令将复制指定的 <src> 路径下的内容到容器中的 <dest> 路径下.
格式为 ADD <src> <dest>
其中 <src> 可以是 Dockerfile 所在目录的一个相对路径(文件或目录), 也可以是 URL, 还可以是一个 tar 文件(如果为 tar 文件, 会自动解压到 <dest> 路径下). <dest> 可以是镜像内的绝对路径, 或者相对于工作目录(WORKDIR) 的相对路径.
路径支持正则格式, 例如:
ADD *.c /code/
9.COPY
格式为COPY <src> <dest>
复制本地主机的 <src>(为 Dockerfile 所在目录的相对路径 文件或目录) 下的内容到镜像中的 <dest> 下. 目标路径不存在时, 会自动创建.
路径同样支持正则格式.
当使用本地目录为源目录时, 推荐使用 COPY.
10.ENTRYPOINT
指定镜像的默认入口命令, 该入口命令会在启动容器时作为根命令执行, 所有传入值作为该命令的参数.
支持两种格式:
ENTRYPOINT ["executable", "param1", "param2"](exec 调用执行)
ENTRYPOINT command param1 param2(shell 中执行)
每个 Dockerfile 中只能有一个 ENTRYPOINT, 当指定多个时, 只有最后一个有效.
在运行时, 可以被 --entrypoint
参数覆盖掉, 如 docker run --entrypoint
.
11.VOLUME
创建一个数据卷挂载点.
格式为 VOLUME ["/data"]
可以从本地主机或其他容器挂载数据卷, 一般用来存放数据库和需要保存的数据等.
12.USER
指定运行容器时的用户名或 UID, 后续的 RUN 等指令也会使用指定的用户身份.
格式为 USER daemon
当服务不需要管理员权限时, 可以通过该命令指定运行用户, 并且可以在之前创建需要的用户. 例如:
RUN groupadd -r postgres && useradd -r -g postgres postgres
要临时获取管理员权限可以使用 gosu 或 sudo.
13.WORKDIR
为后续的 RUN CMD 和 ENTRYPOINT 指令配置工作目录.
格式为 WORKDIR /path/to/workdir
可以使用多个 WORKDIR 指令, 后续命令如果参数是相对路径, 则会基于之前命令指定的路径. 例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为 /a/b/c
14.ARG
指定一些镜像内使用的参数(例如版本号信息等), 这些参数在执行 docker build
命令时才以 --build-arg<varname>=<value>
格式传入.
格式为 ARG<name>[=<defaults value>]
则可以用 docker build --build-arg<varname>=<value>
. 来指定参数值.
15.ONBUILD
配置当所创建的镜像作为其他镜像的基础镜像时, 所执行的创建操作命令.
格式为 ONBUILD [INSTRUCTION]
例如, Dockerfile 使用如下的内容创建了镜像 image-A
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
如果基于 image-A 创建新的镜像时, 新的 Dockerfile 中使用 FROM image-A
指定基础镜像, 会自动执行 ONBUILD 指令的内容, 等价于在后面添加了两条指令:
FROM image-A
#Automatically run the following
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
使用 ONBUILD 指令的镜像, 推荐在标签中注明, 例如 ruby:1.9-onbuild
16.STOPSIGNAL
指定所创建镜像启动的容器接收退出的信号值. 例如:
STOPSIGNAL signal
17.HEALTHCHECK
配置所启动容器如何进行健康检查(如何判断健康与否), 自 Docker 1.12 开始支持.
格式有两种:
HEALTHCHECK [OPTIONS] CMD command: 根据所执行命令返回值是否为0来判断.
HEALTHCHECK NONE: 禁止基础镜像中的健康检查.
OPTION 支持:
--interval=DURATION(默认为: 30s): 过多久检查一次;
--timeout=DURATION(默认为: 30s): 每次检查等待结果的超时;
--retries=N(默认为: 3): 如果失败了, 重试几次才最终确定失败.
18.SHELL
指定其他命令使用 shell 时的默认 shell 类型.
SHELL ["executable", "parameters"]
默认值为 ["/bin/sh", "-c"]
注意: 对于 Windows 系统, 建议在 Dockerfile 开头添加 # escape =`来指定转义信息.
创建镜像
编写完成 Dockerfile 之后, 可以通过 docker build
命令来创建镜像.
基本的格式为 docker build[选项]
内容路径, 该命令将读取指定路径下(包括子目录)的 Dockerfile, 并将该路径下的所有内容发送给 Docker 服务器, 由服务器来创建镜像.
因为除非生成镜像需要, 否则一般建议放置在 Dockerfile 的目录为空目录. 有两点经验:
1.如果使用非内容路径下的 Dockerfile, 可以通过 -f
选项来指定其路径.
2.要指定生成镜像的标签信息, 可以使用 -t
选项.
例如, 指定 Dockerfile 所在路径为 /tmp/docker_builder/
, 并且希望生成镜像标签为 build_repo/first_image
, 可以使用下面的命令:
docker build -t build_repo/first_image /tmp/docker_builder/
使用 .dockerignore 文件
可以通过 .dockerignore 文件(每一行添加一条匹配模式) 来让 Docker 忽略匹配模式路径下的目录和文件. 例如:
# comment
*/temp*
*/*/temp*
tmp?
~*
最佳实践
所谓最佳实践, 实际上是从需求出发, 来定制适合自己 高效方便的镜像.
首先, 要尽量吃透每个指令的含义和执行结果, 自己多编写一些简单的例子进行测试, 弄清除了在撰写正式的 Dockerfile. 此外, Docker Hub 官方仓库中提供了大量的优秀镜像和对应的 Dockerfile, 通过阅读他们来学习如何撰写高效的 Dockerfile.
建议初学者在生成镜像过程中, 尝试从如下角度进行思考, 完善所生成的镜像.
**精简镜像用途: **尽量让每个镜像的用途都比较集中 单一, 避免构造大而复杂 多功能的镜像.
**选用合适的基础镜像: **过大的基础镜像会造成臃肿的镜像, 一般推荐比较小巧的 debian 镜像.
**提供足够清晰的命令注释和维护者的信息: ** Dockerfile 也是一种代码, 需要考虑方便后续扩展和他人使用.
**正确使用版本号: **使用明确的版本号信息, 如1.0, 2.0, 而非 latest, 将避免内容不一致可能引发的惨案.
**减少镜像层数: **如果希望所生成镜像层数尽量少, 则要尽量合并命令, 例如多个 RUN 指令可以合并为一条.
**及时删除临时文件和缓存文件: **特别是在执行 apt-get
指令后, /var/cache/apt
下面会缓存一些安装包.
**高度生成速度: **合理使用缓存, 减少内容目录下的文件, 或使用 .dockerignore
文件指定等.
**调整合理的指令顺序: **在开启缓存的情况下, 内容不变的指令尽量放在前面, 这样可以尽量复用.
**减少外部源的干扰: **如果确实要从外部引入数据, 需要指定持久的地址, 并带有版本信息, 让他人可以重复而不出错.