FROM 指定基础镜像
格式
FROM <镜像>
定制镜像是以一个镜像为基础,在其上进行定制。在一个 Dockerfile
中 FROM
是必备的指令,并且必须是第一条指令。
在 Docker Hub 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 nginx
、redis
、mongo
、mysql
、httpd
、php
、tomcat
等;也有一些方便开发、构建、运行各种语言应用的镜像,如 node
、openjdk
、python
、ruby
、golang
等。
如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntu
、debian
、centos
、fedora
、alpine
等。
除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch
。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。如果以 scratch
为基础镜像的话,意味着不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
RUN 执行命令
格式:
- shell 格式:
RUN <命令>
,就像直接在命令行中输入的命令一样。 - exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
,这更像是函数调用中的格式。
Dockerfile 中每一个指令都会建立一层,RUN
也不例外。每一个 RUN
的行为,都会新建立一层,在其上执行这些命令,执行结束后,commit
这一层的修改,构成新的镜像。
Union FS 是有最大层数限制的,AUFS 不得超过 127 层。
在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。
镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
下面是编译、安装 redis 可执行文件的一个示例:
FROM debian:stretch
RUN set -x; buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-7.2.4.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
CMD 容器启动命令
格式:
shell
格式:CMD <命令>
exec
格式:CMD ["可执行文件", "参数1", "参数2"...]
- 参数列表格式:
CMD ["参数1", "参数2"...]
。在指定了ENTRYPOINT
指令后,用CMD
指定具体的参数。
Docker 不是虚拟机,容器就是进程,既然是进程,在启动容器的时候,需要指定所运行的程序及参数,CMD
指令就是用于指定默认的容器主进程的启动命令的。
在指令格式上,推荐使用 exec
格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 "
,而不要使用单引号。如果使用 shell
格式的话,实际的命令会被包装为 sh -c
的参数的形式进行执行。这就是为什么可以使用环境变量的原因,因为这些环境变量会被 shell 进行解析处理。
Docker 不是虚拟机,容器中的应用都应该以前台执行。
ENTRYPOINT 入口点
格式:
- shell 格式:
ENTRYPOINT <命令>
- exec 格式:
ENTRYPOINT ["可执行文件", "参数1", "参数2"]
ENTRYPOINT
的目的和 CMD
一样,都是在指定容器启动程序及参数。ENTRYPOINT
在运行时也可以替代,不过比 CMD
要略显繁琐,需要通过 docker run
的参数 --entrypoint
来指定。
当指定了 ENTRYPOINT
后,CMD
的含义就发生了改变,不再是直接的运行其命令,而是将 CMD
的内容作为参数传给 ENTRYPOINT
指令,如:
FROM ubuntu:22.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
可添加额外参数 -i
:
docker run myip -i
COPY 复制文件
格式:
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
COPY
指令将从构建上下文目录中 <源路径>
的文件/目录复制到新的一层的镜像内的 <目标路径>
位置,如:
COPY package.json /usr/src/app/
<源路径>
可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match
规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径>
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR
指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
此外,还需要注意一点,使用 COPY
指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。
在使用该指令的时候还可以加上 --chown=<user>:<group>
选项来改变文件的所属用户及所属组。
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/
如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。
ADD 更高级的复制文件
格式:
ADD [--chown=<user>:<group>] <源路径>... <目标路径>
ADD [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
ADD
指令和 COPY
的格式和性质基本一致,但是在 COPY
基础上增加了一些功能。
如果 <源路径>
为一个 tar
压缩文件的话,压缩格式为 gzip
, bzip2
, xz
的情况下,ADD
指令将会自动解压缩这个压缩文件到 <目标路径>
去。
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY
,因为 COPY
的语义很明确,就是复制文件而已,而 ADD
则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD
的场合,就是所提及的需要自动解压缩的场合。
ADD
指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
因此在 COPY
和 ADD
指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY
指令,仅在需要自动解压缩的场合使用 ADD
。
在使用该指令的时候还可以加上 --chown=<user>:<group>
选项来改变文件的所属用户及所属组。
ENV 设置环境变量
格式:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
定义了环境变量,在后续的指令中,就可以使用这个环境变量。通过环境变量,可以让一份 Dockerfile
制作更多的镜像,只需使用不同的环境变量即可。
下列指令可以支持环境变量展开: ADD
、COPY
、ENV
、EXPOSE
、FROM
、LABEL
、USER
、WORKDIR
、VOLUME
、STOPSIGNAL
、ONBUILD
、RUN
。
ARG 构建参数
格式:
ARG <参数名>[=<默认值>]
构建参数和 ENV
的效果一样,都是设置环境变量。所不同的是,ARG
所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但不要使用 ARG
保存密码之类的信息, docker history
是可以看到所有值。
Dockerfile
中的 ARG
指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build
中用 --build-arg <参数名>=<值>
来覆盖。
ARG
指令有生效范围,如果在 FROM
指令之前指定,那么只能用于 FROM
指令中。对于多阶段构建,尤其要注意这个问题。
VOLUME 定义匿名卷
格式:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile
中,可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
VOLUME /data
这里的 /data
目录就会在容器运行时自动挂载为匿名卷,任何向 /data
中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。容器匿名卷目录指定可以通过docker run
命令中指定-v
参数来进行覆盖。
EXPOSE 声明端口
格式:
EXPOSE <端口1> [<端口2>...]
EXPOSE
指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P
时,会自动随机映射 EXPOSE
的端口。
要将 EXPOSE
和在运行时使用 -p <宿主端口>:<容器端口>
区分开来。-p
,是映射宿主端口和容器端口,将容器的对应端口服务公开给外界访问,而 EXPOSE
仅仅是声明容器打算使用什么端口,并不会自动在宿主进行端口映射。
WORKDIR 指定工作目录
格式:
WORKDIR <工作目录路径>
使用 WORKDIR
指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR
会建立目录。
如果需要改变以后各层的工作目录的位置,应该使用 WORKDIR
指令。如果 WORKDIR
指令使用的相对路径,那么所切换的路径与之前的 WORKDIR
有关。
USER 指定当前用户
格式:
USER <用户名>[:<用户组>]
USER
指令和 WORKDIR
相似,都是改变环境状态并影响以后的层。WORKDIR
是改变工作目录,USER
则是改变之后层的执行 RUN
, CMD
以及 ENTRYPOINT
这类命令的身份。
USER
只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
如果以 root
执行的脚本,在执行期间希望改变身份,如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su
或者 sudo
,这些都需要较麻烦的配置,而且在 TTY 缺失的环境下经常出错,建议使用 gosu
。
HEALTHCHECK 健康检查
格式:
HEALTHCHECK [选项] CMD <命令>
:设置检查容器健康状况的命令HEALTHCHECK NONE
:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK
指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。
在没有 HEALTHCHECK
指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。
而自 1.12 之后,Docker 提供了 HEALTHCHECK
指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。
当在一个镜像指定了 HEALTHCHECK
指令后,用其启动容器,初始状态会为 starting
,在 HEALTHCHECK
指令检查成功后变为 healthy
,如果连续一定次数失败,则会变为 unhealthy
。
HEALTHCHECK
支持下列选项:
--interval=<间隔>
:两次健康检查的间隔,默认为 30 秒;--timeout=<时长>
:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;--retries=<次数>
:当连续失败指定次数后,则将容器状态视为unhealthy
,默认 3 次。
和 CMD
, ENTRYPOINT
一样,HEALTHCHECK
只可以出现一次,如果写了多个,只有最后一个生效。
在 HEALTHCHECK [选项] CMD
后面的命令,格式和 ENTRYPOINT
一样,分为 shell
格式,和 exec
格式。命令的返回值决定了该次健康检查的成功与否:0
:成功;1
:失败;2
:保留,不要使用这个值。
ONBUILD 用于延迟构建命令的执行
格式:
ONBUILD <其它指令>
ONBUILD
是一个特殊的指令,它后面跟的是其它指令,比如 RUN
, COPY
等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile
中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD
是为了帮助别人定制自己而准备的。
LABEL 为镜像添加元数据
格式:
LABEL <key>=<value> <key>=<value> <key>=<value>
LABEL
指令用来给镜像以键值对的形式添加一些元数据(metadata)。可以用一些标签来申明镜像的作者、文档地址等:
LABEL org.opencontainers.image.authors="jasperxzy"
LABEL org.opencontainers.image.documentation="https://blog.jasperxzy.com"
具体可以参考 https://github.com/opencontainers/image-spec/blob/master/annotations.md
SHELL 指定所使用的 shell
格式:
SHELL ["executable", "parameters"]
SHELL
指令可以指定 RUN
ENTRYPOINT
CMD
指令的 shell,Linux 中默认为 ["/bin/sh", "-c"]
SHELL
指令在 Windows 上特别有用,Windows 有两种常用且截然不同的本机 shell:cmd
和 powershell
,以及可用的备用 shell,包括 sh
。
SHELL
指令可以出现多次。 每条 SHELL 指令都会覆盖所有先前的 SHELL
指令,并影响所有后续指令。
STOPSIGNAL
格式:
STOPSIGNAL <signal>
设置将发送到容器以退出的系统调用信号。这个信号可以是一个有效的无符号数字,与内核的syscall
表中的位置相匹配,如9
,或者是SIGNAME
格式的信号名,如SIGKILL
。
默认的停止信号是 SIGTERM
,在 docker stop
的时候会给容器内 PID 为 1 的进程发送这个signal ,通过 --stop-signal
可以设置需要的 signal,主要的目的是为了让容器内的应用程序在接收到 signal 之后可以先做一些事情,实现容器的平滑退出。如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,时间默认是 10s 。