FROM
MAINTAINER
LABEL
ENV
ARG
WORKDIR
VOLUME
EXPOSE
COPY
ADD
RUN
CMD
ENTRYPOINT
ONBUILD
HEALTHCHECK
Dockerfile 由一行行命令语句组成,并且支持以 #
开头的注释行。Dockerfile 的内容分为四个部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。例如:
# This dockerfile uses the Ubuntu image
# VERSION 1.0
# Author: wangtingyun
# Command format: Instruction [arguments / command] …
# 第一行必须指定基于的容器镜像
FROM ubuntu
# 维护者信息
MAINTAINER wangtingyun [email protected]
# 镜像的操作指令
RUN echo “deb http://archive.ubuntu.com/ubuntu/ raring main universe” >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo “\ndaemon off;” >> /etc/nginx/nginx.conf
# 容器启动时执行指令
CMD /usr/sbin/nginx
其中:
FROM
功能: 指定一个镜像作为构建自定义镜像的基础镜像,构建的镜像在这个基础镜像之上进行修改定制。
指令格式:
FROM <image>:<tag>
这个指令是 Dockerfile 中的必备指令,同时也必须是第一条指令,如果在同一个 Dockerfile 中创建多个镜像时,可以使用多个 FROM 指令。
MAINTAINER
功能: 指定维护者信息。
格式为:
MAINTAINER <name>
注意: MAINTAINER 已经被抛弃了,建议使用 LABEL 指令。
LABEL
功能: 为镜像添加元数据标签,一个 LABEL 就是一个键值对。
格式如下:
LABEL <key>=<balue> <key>=<balue> ...
示例如下:
LABEL maintainer="[email protected]"
LABEL version="1.0"
可以给镜像添加多个 LABEL,需要注意的是:每条 LABEL 指令都会生成一个新的层。所以最好是把添加的多个 LABEL 合并为一条命令:
# 写在一行,多个键值对使用空格隔开
LABEL maintainer="[email protected]" version="1.0"
# 使用换行符
LABEL maintainer="[email protected]" \
version="1.0"
注意: 如果新添加的 LABEL 和已有的 LABEL 同名,则新值会覆盖掉旧值。
ENV
功能: 设置或定义环境变量,定义的环境变量可以在后续的指令中通过 $
进行使用。
指令格式:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>
示例如下:
# 定义 node 版本的环境变量
ENV NODE_VERSION 7.2.0
# 使用 NODE_VERSION 环境变量
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"
ARG
功能: 设置镜像构建参数。与 ENV 指令作用一致,不过作用域不一样:ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build
的过程中才有效,构建好的镜像内不存在此环境变量。
构建命令 docker build
中可以用 --build-arg
来覆盖。
指令格式:
ARG <key>=<value>
WORKDIR
功能: 指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在(WORKDIR 指定的工作目录,必须是提前创建好的)。
构建镜像过程中的,每一个 RUN 命令都是新建的一层,只有通过 WORKDIR 创建的目录才会一直存在。
指令格式:
WORKDIR <path>
注意: 可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
最终路径为:/a/b/c
VOLUME
功能: 定义挂载匿名数据卷目录。在启动容器时如果忘记挂载数据卷,会自动挂载到匿名卷。
挂载卷的作用:
指令格式:
VOLUME <path>
VOLUME ["" , "" , ...]
重点: 在启动容器 docker run
的时候,我们可以通过 -v
参数修改挂载点。
EXPOSE
功能: 告诉 Docker 服务,容器需要暴露的端口号。
作用:
docker run -P
(大写的 P)时,会自动随机映射 EXPOSE 的端口(而 -p
参数指定具体的映射端口);指令格式:
EXPOSE <port1> [<port2> ...]
COPY
功能: 从上下文目录中复制文件或者目录到容器里指定路径。
指令格式:
COPY [--chown=<user>:<group>] <源路径1>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
[--chown=:]
:可选参数,用于改变复制到容器内文件的拥有者和所属组;<源路径>
:源文件或目录(以 Dockerfile 的上下文环境为相对路径),可以使用通配符,但通配符要满足 Go
的 filepath.Match
规则;<目标路径>
:容器内的路径,如果路径不存在的话,会自动创建;示例:
# 复制单个文件
COPY data01.txt /home/data/
# 复制多个文件,根据通配符匹配
COPY data*.txt /home/data/
# 复制文件夹
COPY temp/data/ /home/data/
注意: 目标路径最后一定是以 /
结尾的,否则就是视目标路径为文件,相当于拷贝文件并重命名为目标路径最后的名称。
ADD
功能: 从上下文目录中复制文件或者目录到容器里指定路径,功能和 COPY 指令类似。
指令格式:
ADD <src> <dest>
优缺点:
tar
压缩文件的话,压缩格式为 gzip, bzip2
以及 xz
的情况下,会自动复制并解压到 <目标路径>;tar
压缩文件,会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。所以,具体是否使用 ADD 指令,可以根据是否需要自动解压来决定。
RUN
作用: 为构建的镜像指定要运行的命令行命令,而这些命令是在 docker build
的时候执行的。
指令格式有两种:
1、shell
格式
RUN <command>
2、exec
格式
RUN ["executable", "param1", "param2", ...]
exec
命令执行,可以指定其他终端,例如:RUN ["/bin/bash", "-c", "echo hello"]
;每条 RUN 指令将在当前镜像的基础上执行指定命令,并提交为新的镜像,而 Dockerfile 的指令每执行一次都会在 docker 上新建一层,过多无意义的层,会造成镜像膨胀过大,所以建议将多个命令合并到同一个 RUN 指令上,例如:
# 多个 RUN 指令合并到同一行
RUN yum -y install wget \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& tar -xvf redis.tar.gz
通过使用 &&
符号连接命令,这样执行后,只会创建 1 层镜像。
CMD
作用: 为启动的容器指定要运行的命令,类似于 RUN 指令,但 CMD 运行程序的时间是在 docker run
时执行的,命令运行结束,容器也就结束。
格式有三种:
# 1、shell 命令格式
CMD <shell command>
# 2、exec 命令格式:推荐
CMD ["<可执行文件或命令>","" ,"" ,...]
# 3、该写法是为 ENTRYPOINT 指令指定的程序提供默认参数
CMD ["" ,"" ,...]
推荐使用第二种格式,执行过程比较明确,而第一种格式实际上在运行的过程中也会自动转换成第二种格式运行,并且默认可执行文件是 sh
。
注意:
docker run
命令行参数中指定了要运行的程序命令,则会覆盖CMD 指令指定的程序命令。ENTRYPOINT
作用: 和 CMD 指令类似,也是指定容器启动时执行的命令,但和 CMD 指令不同的是:
docker run
的命令行参数指定了要运行的程序命令,ENTRYPOINT 指令的程序命令也不会被覆盖,并且这些命令行参数会被当作参数传送给 ENTRYPOINT 指令指定的程序;docker run
时使用了 --entrypoint
选项,将覆盖 ENTRYPOINT 指令指定的程序。指令格式:
ENTRYPOINT ["" , "" , "" , ...]
注意: 每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个 ENTRYPOINT 时,只有最后一个生效。
使用方式:
ENTRYPOINT 指令一般搭配 CMD 指令一起使用:通过 CMD 的第三种指令格式给 ENTRYPOINT 动态传参。
示例: 构建 nginx:test
镜像
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参
1、当 docker run
不传参运行时:
docker run nginx:test
容器内会默认运行以下命令(命令参数来自 CMD 指定提供的参数),启动主进程:
nginx -c /etc/nginx/nginx.conf
2、当传参运行时:
docker run nginx:text -c /etc/nginx/new.conf
容器会运行以下命令(命令行参数覆盖 CMD 指令提供的参数),启动主进程:
nginx -c /etc/nginx/new.conf
ONBUILD
功能: 配置当所创建的镜像作为其他新创建镜像的基础镜像时,所执行的操作指令。
指令格式:
ONBUILD [INSTRUCTION]
示例:
使用如下 Dockerfile 内容创建镜像 myImage
...
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build –dir /app/src
...
当新的 Dockerfile 中基于镜像 myImage (即使用 FROM myImage
)时,会自动执行 ONBUILD 指令的内容,等价于在后面添加了两条指令:
FROM myImage
# 自动运行以下两条指令
ADD . /app/src
RUN /usr/local/bin/python-build –dir /app/src
HEALTHCHECK
功能: 指定某个程序或者指令来监控 docker 容器服务的运行状态。
指令格式:
# 设置检查容器健康状况的命令
HEALTHCHECK [选项] CMD <命令>
# 如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK NONE
# 这边 CMD 后面跟随的命令使用,可以参考 CMD 的用法
HEALTHCHECK [选项] CMD <命令>
.dockerignore
文件.dockerignore
的作用和语法类似于 .gitignore
,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少 Docker 镜像的大小。
如果在 Docker 容器中运行多个进程会有比较多的麻烦:
所以,建议每个应用构建单独的 Docker 镜像,然后使用 Docker Compose
运行多个 Docker 容器。
由于 Docker 镜像是分层的:
所以,RUN 指令的多个命令可以通过换行符 \
和 &&
来合并成一行指令,例如下面的 node.js
和 npm
模块的安装命令:
FROM ubuntu
ADD . /app
RUN apt-get update \
&& apt-get install -y nodejs \
&& cd /app \
&& npm install
CMD npm start
注意: 们只能将变化频率一样的指令合并在一起,将 node.js
安装与 npm
模块安装放在一起的话,则每次修改源代码,都需要重新安装 node.js
,这显然不合适。因此,正确的写法是这样的:
FROM ubuntu
RUN apt-get update && apt-get install -y nodejs
ADD . /app
RUN cd /app && npm install
CMD npm start
latest
当镜像没有指定标签时,将默认使用 latest
标签。因此, FROM ubuntu
指令等同于 FROM ubuntu:latest
。
但是,当镜像更新时,latest
标签会指向不同的镜像,这时构建镜像有可能失败。如果你的确需要使用最新版的基础镜像,可以使用 latest
标签,否则的话,最好指定确定的镜像标签。
假设我们更新了 apt-get
源、下载、解压并安装了一些软件包,它们都保存在 /var/lib/apt/lists/
目录中。但是,运行应用时,Docker 镜像中并不需要这些文件。我们最好将它们删除,因为它会使 Docker 镜像变大:
RUN apt-get update \
&& apt-get install -y nodejs \
# 删除多余文件
&& rm -rf /var/lib/apt/lists/*
alpine
版本最好)比如,我们只是构建运行 node 程序,没有必要选择 ubuntu
这种通用的基础镜像:
FROM ubuntu
选择 node
镜像作为基础镜像更好,并且选择 alpine 版本的更好:
FROM node:7-alpine
alpine
是一个极小化的 Linux 发行版,非常适合作为基础镜像;apk
是 Alpine 的包管理工具,它与 apt-get
有些不同,但是非常容易上手。另外,它还有一些非常有用的特性,比如 no-cache
和 --virtual
选项,它们都可以帮助我们减少镜像的大小;WORKDIR 指令可以设置默认目录,也就是运行 RUN / CMD / ENTRYPOINT 指令的地方。
ENTRYPOINT 指令并不是必须的,因为它会增加复杂度。ENTRYPOINT 是一个脚本,它会默认执行,并且将指定的命令作为其参数,它通常用于构建可执行的 Docker 镜像。
在 Dockerfile 中指定 entrypoint.sh
脚本:
ENTRYPOINT ["./entrypoint.sh"]
然后在 docker run
中执行该脚本:
docker run -it demo /bin/bash
注意: entrypoint
脚本,应该使用 exec
执行命令,否则无法顺利地关闭容器,因为 SIGTERM 信号会被 bash 脚本进程吞没。exec
命令启动的进程可以取代脚本进程,因此所有的信号都会正常工作。
COPY 指令非常简单,仅用于将文件拷贝到镜像中,而 ADD 相对来讲复杂一些,可以用于下载远程文件以及解压压缩包。所以除非要解压缩包,否则建议使用 COPY。
我们应该把变化最少的部分放在 Dockerfile 的前面,这样可以充分利用镜像缓存。
运行 Docker 容器时很可能需要一些环境变量。在 Dockerfile 设置默认的环境变量、端口映射和数据卷是一种很好的方式:
# 设置环境变量
ENV MEDIA_DIR=/media \
APP_PORT=3000
# 挂载数据卷
VOLUME $MEDIA_DIR
# 暴露映射端口
EXPOSE $APP_PORT
ENV 指令指定的环境变量在容器中可以使用,如果只是需要指定构建镜像时的变量,可以使用 ARG 指令。
使用 LABEL 指令,可以为镜像设置元数据,例如镜像创建者或者镜像说明。旧版的 Dockerfile 语法使用 MAINTAINER 指令指定镜像创建者,但是它已经被弃用了:
FROM node:7-alpine
LABEL maintainer "[email protected]"
...
运行容器时,可以指定 --restart always
选项。这样的话,容器崩溃时,Docker 守护进程会重启容器,对于需要长时间运行的容器,这个选项非常有用。
但是,如果容器的确在运行,但是不可用(陷入死循环,配置错误)怎么办?使用 HEALTHCHECK 指令可以让 Docker 周期性的检查容器的健康状况。
我们只需要指定一个命令,如果一切正常的话返回 0,否则返回 1:
HEALTHCHECK CMD curl --fail http://localhost:3000 || exit 1
FROM node:7-alpine
LABEL maintainer "[email protected]"
ENV PROJECT_DIR=/app
WORKDIR $PROJECT_DIR
COPY package.json $PROJECT_DIR
RUN npm install
COPY . $PROJECT_DIR
ENV MEDIA_DIR=/media \
NODE_ENV=production \
APP_PORT=3000
VOLUME $MEDIA_DIR
EXPOSE $APP_PORT
HEALTHCHECK CMD curl --fail http://localhost:$APP_PORT || exit 1
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]