Docker 006 Dockerfile 指令
Docker 006 Dockerfile 指令
前面我们在构建镜像的时候已经使用了一些Dockerfile 中的指令,比如 FROM、RUN、EXPOSE,其实还有很多其他的指令可供我们使用。
FROM
一个有效的 Dockerfile 文件必须以为 FROM 指令开始,他的作用是指定一个新构建镜像的初始镜像,后续其他指令都基于 FROM 指定的镜像。
# 三种格式
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
--platform
可以用于指定镜像的来源的平台,以防FROM指令引用了一个多平台的镜像,比如linux/amd64, linux/arm64, or windows/amd64
等平台,值还可以使用全局参数
tag
和digest
值也是可选的,如果不使用的话builder会自动为我们的新镜像分配一个latest
作为默认tag.
可选项AS name
可以为新构建的镜像起一个名字,方便记忆,name
还可以被用于接下来的FROM
和COPY --from=
指令引用.
ARG 指令是唯一一个可能出现在 FROM 之前的指令,
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
#######################
#
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
Dockerfile 中的FROM指令可以出现多次,出现的每一条 FROM 指定都是一个构建阶段,生成的镜像只能是最后一个阶段的结果,但能将前置阶段的内容拷贝到后边的阶段中。
RUN
RUN指令有两种形式:
RUN <command> # shell形式,命令会在shell中执行,Linux 下默认是/bin/sh -c, win下默认是cmd /S /C
RUN ["executable", "param1", "param2"] # exec 形式
RUN指令会在当前镜像层的顶层执行命令,并提交结果,即产生新的一层,新产生的层可以用与 dockerfile 的下一步中。
exec 形式可以避免破坏 shell 字符串,并使用不包含指定 shell 的基本镜像。
shell 形式的默认 shell 可以通过 shell 命令进行修改,比如不想使用/bin/sh,想使用/bin/bash,可通过如下形式:
# 当一行内容太长时,可使用反斜线 进行分割, 依旧认为是一条指令
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME
或者
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
或者
RUN ["/bin/bash", "-c", "echo hello"] # 推荐使用此形式
注意:
- exec 形式会被解析为 json 数组,也就是说字符串周围必须是双引号。
- exec 形式不会调用 shell,意思是说,有些 shell 处理不会发生,例如RUN [ "echo", "$HOME" ]中$HOME不会发生变量替换,如果你想要 shell 来做这些处理,可以直接使用 shell 形式或者指定 shell 来执行,例如RUN echo $HOME 或者 RUN [ "sh", "-c", "echo $HOME" ]。 在exec 形式中指定 shell 的时候,变量的解析由 shell 来执行,而不是 docker。
多次构建时,RUN 指令的 cache 不会自动失效,例如RUN apt-get dist-upgrade -y的缓存会被再次使用,可以使用--no-cache参数让缓存无效,例如:docker build --no-cache。
CMD
CMD指令用于指定一个容器启动时要运行的命令。类似于 RUN 指令,区别是 RUN 指令是指定镜像被构建时要运行的命令,CMD 是指定容器被启动时要运行的命令。和使用 docker run 命令启动容器时,要运行的命令非常相似,例如:
$ docker run -it web01 /bin/bash
# 等价于 Dockerfile 中的
CMD ["/bin/bash"]
# CMD 指令后的命令还可以加参数
CMD ["/bin/bash", "-l"]
CMD 支持的种格式
CMD ["executable","param1","param2"] # 使用 exec 执行,推荐方式;
CMD command param1 param2 # 在 /bin/sh 中执行,提供给需要交互的应用,可能会导致意外
需要注意的是 docker run 命令可以覆盖 CMD指令,如果在 Dockerfile 中指定了 CMD 指令,同时在 docker run 命令中也指定了运行的命令,那么CMD 指令中的命令会被覆盖。
还有就是 在 Dockerfile 中只鞥你指定一条 CMD指令,如果指定了多条 CMD指令,那么只有最后一条 CMD指令会被使用,
ENTRYPOINT
ENTRYPOINT 指令和 CMD 指令非常相似,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。
但是, 如果运行 docker run 时使用了 --entrypoint 选项,此选项的参数可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。
优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。
注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
ENTRYPOINT ["/usr/sbin/nginx"]
# 和 CMD 指令一样,可通过数组的方式为命令添加响应的参数
ENTRYPOINT ["/usr/sbin/nginx", "-t"]
ENTRYPOINT 指令还可以和 CMD 指令搭配使用
# 假设 Dockerfile 中有如下内容
ENTRYPOINT ["nginx", "-c"]
CMD ["/etc/nginx/nginx.conf"]
# 默认执行,不传递参数
$ docker run nginx:test # 这个相当于直接启动一个 nginx 容器
#此时容器中运行的命令是
$ nginx -c /etc/nginx/nginx.conf
# 指定参数运行,会传递参数
$ docker run nginx:test -c /etc/nginx/new.conf
#此时容器中运行的命令是
$ nginx -c /etc/nginx/new.conf
WORKDIR
WORKDIR指令的作用是 在从镜像创建一个新容器时,为 RUN,CMD,ENTRPOINT,COPY和ADD指令指定工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
# pwd命令的输出/a/b/c
docker run 命令中的 -w 参数可以覆盖 WORKDIR 中的工作目录
$ docker run -it -w /var/log ubuntu pwd
# 此时容器内的工作目录是 /var/log
ENV
ENV指令用来在镜像的构建过程中设置环境变量。
# key为变量名, value 为变量的值
ENV key value # 设置一个变量
ENV key1=value1 key2=value2 # 设置多个变量
通过 ENV指令设置的变量可以在后续其他指令中继续使用。
USER
USER 指令用来指定镜像会以哪个用户来运行,
USER nginx
基于该镜像启动的容器会以nginx 用户的身份来运行,还可以实行用户名或UID以及组或者 GID,或者两者的组合
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group
还可以在 docker run 命令中以-u参数来覆盖改指令的值
VOLUME
VOLUME指令用来向基于镜像创建的容器添加卷,一个卷可以是一个或多个容器内特定的目录,该目录可以绕过联合文件系统,并提供以下功能:
- 卷可以在容器建共享和重用
- 一个容器不是必须和其他容器共享卷
- 对卷的修改是立即生效的
- 对卷的修改不会对更新镜像产生影响
- 卷会一直存在,直到没有任何容器再使用它
使用卷功能,可以把数据或某些内容添加到镜像而不是提交到镜像上,还可以在多个容器间共享这些内容。
# 所有基于此进行启动的容器,都会创建挂载点:/opt/web
VOLUME ["/opt/web"]
VOLUME ["路径1", "路径2", ...]
卷服务有有个作用是:
- 可避免容器不断变大
- 避免重要数据,因 容器重启而丢失
ADD
ADD 指令用来将构建环境下的文件和目录复制到镜像中,ADD指令需要指定源文件位置和目标文件位置两个参数。
ADD src dst
# 将构建目下下的 sofaware.lic 文件拷贝到 /opt/web/software.lic
ADD sofaware.lic /opt/web/software.lic
ADD http://re2do.com/test.zip /opt/web/test.zip
# 如果源是归档文件,docker 会将其解开,docker在这里的行为和 tar -x 一样
# 该指令执行后的输入原目标目录+归档文件中的内容,如果目标目录下存在与源文件同名的目录或文件,那么目标目录下的文件或目录不会被覆盖
# 缺点也很明显,不解压无法复制归档文件
ADD test.tar.gz /opt/web/test/
源文件可以是一个 URL、或者构建上下文或环境中的文件或者目录,且不能是构建目录或者上下文之外的文件。Docker 通过目的地址的字符判断文件源是目录还是文件,如果以”/“结尾,那么 Docker 就任务源是目录,如果不以”/“结尾,就认为源是文件。
COPY
COPY 指令类似于 ADD 指令,区别是,COPY 只从上下文目录中复制文件或目录到指定路径。
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] # 路径中有空格的需要使用此形式
# [--chown=<user>:<group>]:可选参数,仅适用于构建linux容器,用户改变复制到容器内文件的拥有者和属组,不指定时属主属组都为 0
把src 中的文件或目录复制到容器中的 dst 路径中,src 中可能包含通配符:
COPY hom* /mydir/ # 复制所有以 home 开头的文件
COPY hom?.txt /mydir/ # 问号? 表示任意的单字符
目标路径是绝对路径或者 WORKDIR的相对路径
COPY test relativeDir/ # 把 test复制到 `WORKDIR`/relativeDir/
COPY test /absoluteDir/ # 把 test复制到 /absoluteDir/
在复制的文件或目录的路径有有特殊字符时,需要按照 golang 的规则将特殊字符转义,例如复制名为arr [0] .txt的文件:
COPY arr[[]0].txt /mydir/ # 复制文件"arr[0].txt" 到 /mydir/
LABEL
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."
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
# 使用 docker inspect 命令查看镜像中的 label 信息、
$ docker inspect image_name
"Labels": {
"com.example.vendor": "ACME Incorporated"
"com.example.label-with-value": "foo",
"version": "1.0",
"description": "This text illustrates that label-values can span multiple lines.",
"multi.label1": "value1",
"multi.label2": "value2",
"other": "value3"
},
ARG
ARG指令用于定义一个变量,在docker build命令使用--build-arg =参数时,可以将使用时传递给运行时的变量,
一个变量,在构建时,用户可以传给给构建者,当 docker build 使用 --build-arg =参数时,可以将值传递给该变量。
# Dockerfile 文件部分内容
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
# 要执行的 docker build 命令
$ docker build --build-arg CONT_IMG_VER=v2.0.1 .
# 在这里例子中,ARG 指令中的CONT_IMG_VER变量会被替换为 v2.0.1 ,
# 然后 ENV 指令做了变量替换,如果CONT_IMG_VER未被设置,则会使用v1.0.0
ONBUILD
当镜像是其他镜像的基础镜像时,使用ONBUILD指令可以添加一条触发指令,以便稍后执行;触发指令会在下游的构建上下文中执行,就好像它已经被插入到下游 Dockfile 的 FROM指令 之后一样。触发指令可以是任何构建指令。
# 示例:可能添加了 如下内容
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
注意:
- 不允许出现 ONBUILD ONBUILD 这种形式的 ONBUILD 链
- ONBUILD指令可能不会触发 FROM 或者MAINTAINER(废弃) 指令
STOPSIGNAL
STOPSIGNAL指令用来设置停止容器时发送什么系统调用信号给容器,且这个信号必须是内核系统调用表中合法的数字(例如 9)或者 SIGNAME 格式中的信号名称(如 SIGKILL)。
# 格式
STOPSIGNAL signal
HEALTHCHECK
HEALTHCHECK指令有两种形式 :
# 第一种形式:通过在容器内部运行一个命令来检查容容器的健康状态
HEALTHCHECK [OPTIONS] CMD command
# 第二种形式:禁用从基本映像继承的任何健康状态检查
HEALTHCHECK NONE
在 CMD 之前可以使用的选项如下:
--interval=DURATION
(default:30s
): ,从容器运行起来开始计时,interval秒进行第一次健康检查,随后每间隔interval秒进行一次健康检查--timeout=DURATION
(default:30s
):执行command需要时间,比如curl 一个地址,如果超过timeout秒则认为超时是错误的状态,此时每次健康检查的时间是timeout+interval秒。--start-period=DURATION
(default:0s
): 启动时间, 默认 0s, 如果指定这个参数, 则必须大于 0s 。为需要启动的容器提供了初始化的时间段, 在这个时间段内如果检查失败, 则不会记录失败次数。 如果在启动时间内成功执行了健康检查, 则容器将被视为已经启动, 如果在启动时间内再次出现检查失败, 则会记录失败次数。--retries=N
(default:3
): 连续检查retries次,如果结果都是失败状态,则认为这个容器是unhealth的
Dockerfile 中只会有一条HEALTHCHECK指令有效,如果出现多次HEALTHCHECK,那么只有最后一次的会生效。
关键字 CMD之后的命令和 ENTRYPOINT一样,可以是一个 shell 格式(HEALTHCHECK CMD /bin/check-running)或者exec 格式。
命令的返回值可以表明容器的健康状态:
- 0 :成功,容器是健康的,可以使用
- 1 : 失败,容器没有正确运行
- 2 :保留,不要使用这个返回值
例如,每 5 分钟请求一次网站首页,如果 3 秒内无响应就认为失败:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
为了便于调试,该命令在stderr或stdout 的任何输出(UTF-8编码)都会被存储在健康状态中,可使用 docker inspect 命令查询到。此类输出应该简短(当前值保存钱 4096 个字节)。
当容器的健康状态发生变化时,将以新状态生成一个health_status事件。
SHELL
SHELL 指令允许覆盖默认的shell 形式命令的默认 shell,在 Linux 中,默认的 shell 是["/bin/sh", "-c"],Win 上的是["cmd", "/S", "/C"]。
SHELL 指令必须是 JSON 格式:
# SHELL 指令格式:
SHELL ["executable", "parameters"]
SHELL 指令在 win 上特别有用;Win 上有两个不同的shell:cmd 和 powershell,以及别用 shell:sh。
SHELL 指令可以出现多次,每个 SHELL 指令会覆盖前面的 SHELL 指令,并影响后续的所有指令。例如:
FROM microsoft/windowsservercore
# Executed as cmd /S /C echo default
RUN echo default
# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default
# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello
# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello
当在 Dockerfile 中使用RUN
, CMD
和ENTRYPOINT
指令的 shell 形式,他们可能会受到 SHELL 指令的影响。
下面的例子是Win 上常见的形式,可以使用 SHELL 指令进行精简:
...
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
...
# docker 调用的命令将会如下:
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
这样做会效率低下,有两个原因。
- 第一:有一个不必要的 cmd.exe 被调用
- 第二:使用 shell 格式的 RUN 指令都需要额外的命令前缀:powershell -command
为了变的更高效,可以采用两种机制之一。
第一种:使用JSON 形式的 RUN 指令:
...
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
...
第二种:使用 SHELL 指令的 shell 形式。
# escape=`
FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'
# 执行结果如下:
PS E:\docker\build\shell> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
---> Running in 6fcdb6855ae2
---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
---> Running in d0eef8386e97
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 10/28/2016 11:26 AM Example
---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
---> Running in be6d8e63fe75
hello world
---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\docker\build\shell>
SHELL 指令还可以用于修改 shell 的运行方式,例如在 win 上使用SHELL cmd /S /C /V:ON|OFF
.
如果需要备用shell(例如 zsh、csh、tcsh 等),也可以使用 SHELL 指令。