326 lines
15 KiB
Markdown
326 lines
15 KiB
Markdown
# Dockerfile
|
||
Docker可以通过读取dockerfile中的指令自动构建image。
|
||
## format
|
||
如下是dockerfile的格式:
|
||
```dockerfile
|
||
# Comment
|
||
INSTRUCTION arguments
|
||
```
|
||
指令是不区分大小写的,但是习惯将其大写以区分INSTRUCTION和arguments。
|
||
Dockerfile中的指令将会被按顺序执行,dockerfile必须以FROM指令开头。FROM指令制定了构建的Parent image。
|
||
## parser directive
|
||
parser指令是可选的,并影响dockerfile后续的处理方式。parser指令并不会向build过程中添加layer,也不会展示为一个build过程。parser指令的格式如下所示:
|
||
```dockerfile
|
||
# directive=value
|
||
```
|
||
一旦任何comment、空行或builder instruction被处理,docker将不再接受parser directive,即使后面有格式符合parser instruction的行也会被当作注释处理。故而parser instruction必须位于dockerfile的最顶端。
|
||
parser directive是大小写不敏感的,但是推荐将其小写。约定也要求在任何parser directive后添加一个空白行。
|
||
**如下格式是无效的:**
|
||
```dockerfile
|
||
# direc \
|
||
tive=value
|
||
```
|
||
**如下格式中parser指令连续出现了两次,也是无效的:**
|
||
```dockerfile
|
||
# directive=value1
|
||
# directive=value2
|
||
|
||
FROM ImageName
|
||
```
|
||
**由于之前已经有一条指令,如下parser指令将会被当作注释处理:**
|
||
```dockerfile
|
||
FROM ImageName
|
||
# directive=value
|
||
```
|
||
**由于之前存在一条注释,故而parser指令也会被当作注释处理:**
|
||
```dockerfile
|
||
# About my dockerfile
|
||
# directive=value
|
||
FROM ImageName
|
||
```
|
||
**不被识别的parser指令也会被当作注释处理,由于上一条parser指令不被识别被当作注释,故而第二条能被识别的parser指令也会被当作注释处理:**
|
||
```dockerfile
|
||
# unknowndirective=value
|
||
# knowndirective=value
|
||
```
|
||
**非换行空白符允许在parser指令中出现,故而下列格式的parser instruction都是允许的:**
|
||
```dockerfile
|
||
#directive=value
|
||
# directive =value
|
||
# directive= value
|
||
# directive = value
|
||
# dIrEcTiVe=value
|
||
```
|
||
dockerfile支持如下两条parser directive:
|
||
- syntax
|
||
- escape
|
||
### syntax
|
||
该特性仅当使用了BuildKit backend时可用,当使用classic builder backend时会被忽略。
|
||
### escape
|
||
```dockerfile
|
||
# escape=\ (backslash)
|
||
```
|
||
or
|
||
```dockerfile
|
||
# escape=` (backtick)
|
||
```
|
||
escape指令用于设置dockerfile中的转义符。如果没有显式设置的情况下,默认情况下转义符为\\
|
||
转义符在dockerfile中既用于对行内的字符进行转义,也用于对换行符进行转义,\后跟一个换行符可以将一行指令拆分为多行进行编写。
|
||
## ENV
|
||
环境变量(dockerfile中以ENV开始的变量)可以在其他指令中作为变量被使用,由dockerfile进行解析。
|
||
环境变量在dockerfile中通过$variable_name或${variable_name}的形式进行使用。
|
||
${variable_name}支持一些标准的bash使用:
|
||
- ${variable:-word}:如果variable被设置,那么返回值为variable设置的值;如果variable没有被设置,那么返回值为word
|
||
- \${variable:+word}:如果variable被设置,那么返回word,否则返回空字符串
|
||
可以通过\符号对$进行转义
|
||
```dockerfile
|
||
FROM busybox
|
||
ENV FOO=/bar
|
||
WORKDIR ${FOO} # WORKDIR /bar
|
||
ADD . $FOO # ADD . /bar
|
||
COPY \$FOO /quux # COPY $FOO /quux
|
||
```
|
||
在dockerfile中,如下命令都支持使用环境变量:
|
||
- ADD
|
||
- COPY
|
||
- ENV
|
||
- EXPOSE
|
||
- FROM
|
||
- LABEL
|
||
- STOPSIGNAL
|
||
- USER
|
||
- VOLUME
|
||
- WORKDIR
|
||
- ONBUILD (when combined with one of the supported instructions above)
|
||
在ENV中使用环境变量时,环境变量的值使用的是上一条ENV指令结束时的值:
|
||
```dockerfile
|
||
ENV abc=hello
|
||
ENV abc=bye def=$abc
|
||
ENV ghi=$abc
|
||
```
|
||
其中def的值为hello,而ghi的值为bye。
|
||
## .dockerignore file
|
||
当docker cli将context发送给docker daemon之前,其会先在context根目录查找文件名为.dockerignore的文件。如果该.dockerignore文件存在,docker cli将会删除context中符合pattern的文件。者可以避免务必要的发送大文件或敏感文件。
|
||
docker cli将.dockerignore解释为用换行符分隔的一系列模式,**context的根目录既被当作工作目录,也被当作根目录**。
|
||
> 在.dockerignore中,#开头的行将被看作注释。
|
||
|
||
.dockerignore的语法如下:
|
||
| rule | behave |
|
||
| :-: | :-: |
|
||
| # comment | 注释,忽略 |
|
||
| \*/temp\* | 排除了根目录的直接子目录中任何以temp开头的文件和目录,例如/somedir/temporary.txt或/somedir/temp |
|
||
| \*/\*/temp\* | 排除深度为2的子目录中任何以temp开头的文件或者目录,例如/somedir/subdir/temporary.txt is excluded |
|
||
| temp? | 排除根目录的子目录中名称为temp+任意字符的文件和目录 |
|
||
| \*\*/\*.go | **用于匹配任意层(包含0)的路径,此pattern会排除任何以.go结尾的文件 |
|
||
|
||
当pattern以!开头时,代表不排除满足pattern的文件
|
||
```.dockerignore
|
||
*.md
|
||
!README.md
|
||
```
|
||
上述代表排除所有.md文件,但是不排除README.md
|
||
> 在.dockerignore文件中匹配的最后一条pattern将会决定该文件是否包含在context中
|
||
```.dockerignore
|
||
*.md
|
||
!README*.md
|
||
README-secret.md
|
||
```
|
||
上诉代表所有的.md文件都会被移除,但是满足README*.md格式的文件将会被保留,README-secret.md文件会被移除。
|
||
> 如果想要在.dockerignore中指定哪些文件要被包含,而不是指定哪些文件被排除,可以使用如下形式
|
||
```.dockerignore
|
||
*
|
||
!include-pattern...
|
||
```
|
||
## FROM
|
||
FROM命令格式如下:
|
||
```dockerfile
|
||
FROM [--platform=<platform>] <image> [AS <name>]
|
||
```
|
||
or
|
||
```dockerfile
|
||
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
|
||
```
|
||
or
|
||
```dockerfile
|
||
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
|
||
```
|
||
FROM命令设置了一个初始build状态,并且为其他命令设置了一个初始的镜像。一个有效的dockerfile文件必须以FROM命令开始。
|
||
- 在同一个dockerfile中,FROM命令可以出现多次,用于创建多个镜像或将其中一个build stage作为另一个的依赖。只需要在每条新的FROM语句之前记录上一个由commit输出的image id,每条新FROM语句都会清除之前语句创建的所有状态。
|
||
- 可以为一个新的build stage指定一个名称,通过在FROM语句之后添加AS NAME,该名称可以在来连续的FROM语句或COPY --from=\<name\>中被使用。
|
||
- tag或digest值也是可选的,如果忽略,那么会使用带lattest标签的值。
|
||
如果FROM引用了一个多平台的镜像,那么--platform可以被使用,例如linux/amd64, linux/arm64, or windows/amd64.默认情况下,会使用build请求目标平台的值(--platform=$BUILDPLATFORM)。
|
||
### ARG和FROM的交互
|
||
FROM指令支持使用ARG定义的变量,例如:
|
||
```dockerfile
|
||
ARG CODE_VERSION=latest
|
||
FROM base:${CODE_VERSION}
|
||
CMD /code/run-app
|
||
|
||
FROM extras:${CODE_VERSION}
|
||
CMD /code/run-extras
|
||
```
|
||
位于FROM指令之前的的ARG指令是不被包含在build stage中的,故而无法被FROM之后的指令使用。如果想要使用在FROM之前声明的ARG变量的值,可以在build stage之内使用一个没有值的ARG指令,如下所示:
|
||
```dockerfile
|
||
ARG VERSION=latest
|
||
FROM busybox:$VERSION
|
||
# 再次声明VERSION,以此使用FROM之前的VERSION值
|
||
ARG VERSION
|
||
RUN echo $VERSION > image_version
|
||
```
|
||
## RUN
|
||
RUN命令具有两种格式:
|
||
- RUN \<command\>:shell格式,该命令默认是跑在shell中的,默认情况下是/bin/sh -c
|
||
- RUN ["executable", "param1", "param2"]
|
||
RUN指令将会在新的layer中执行任何command并且将结果提交,提交结果镜像将会作为dockerfile中下一步操作的镜像。
|
||
在shell格式中,可以使用转义符来将RUN命令拓展到多行:
|
||
```dockerfile
|
||
RUN /bin/bash -c 'source $HOME/.bashrc && \
|
||
echo $HOME'
|
||
# 其和如下形式命令等效
|
||
RUN /bin/bash -c 'source $HOME/.bashrc && echo $HOME'
|
||
```
|
||
如果想要使用其他的shell,可以使用如下形式:
|
||
```dockerfile
|
||
RUN ["/bin/bash", "-c", "echo hello"]
|
||
```
|
||
RUN指令的缓存在下次build操作执行时并不会失效,在下次build操作时会被重用。RUN指令的缓存可以通过指定--no-cache选项被失效,使用示例如下:docker build --no-cache
|
||
## RUN --mount
|
||
RUN --amount允许创建一个文件系统挂载,build操作可以访问该文件系统。
|
||
syntax:
|
||
```dockerfile
|
||
RUN --mount=[type=<TYPE>][,option=<value>[,option=<value>]...]
|
||
```
|
||
### Mount Types
|
||
挂载类型如下:
|
||
- bind(default):绑定装载上下文目录(只读)
|
||
- cache:挂载一个临时目录到缓存目录,用于编译和包管理
|
||
- secret:允许build container访问secure文件(例如私钥)而无需将私钥拷贝到image中
|
||
- ssh:允许build container通过ssh agent访问ssh key
|
||
### RUN --mount=type=bind
|
||
该mount type允许将文件或者目录绑定到build container中,默认情况下一个bind-mount是只读的。
|
||
| OPTION | DESCRIPTION |
|
||
| :-: | :-: |
|
||
| `target` | 挂载到的目录 |
|
||
| `source` | `from`中的source path |
|
||
| `from` | `source`根路径的build stage或image name,默认情况下为build context |
|
||
| `rw`,`readwrite` | 允许在mount后的文件系统执行写入操作,写入信息将会被丢弃 |
|
||
> RUN --mount=type=bind允许将context或镜像中的目录绑定到build container中,并且只有在该条RUN指令运行时,才可以访问挂载的目录。
|
||
## RUN --network
|
||
控制该命令运行在哪种网络环境下,支持如下网络环境:
|
||
`syntax`:RUN --network=type
|
||
- default:运行在默认网络环境下
|
||
- none:运行在无网络访问的环境下
|
||
- host:运行在宿主机的网络环境下
|
||
## CMD
|
||
CMD命令具有三种形式:
|
||
- CMD ["executable","param1","param2"] (exec form)
|
||
- CMD ["param1","param2"] (作为ENTRYPOINT的默认参数)
|
||
- CMD command param1 param2 (shell form)
|
||
|
||
在dockerfile中,只有一条CMD指令能够生效,如果dockerfile中存在多条CMD指令,那么只有最后一条CMD指令能够生效。
|
||
CMD指令的主要作用是为执行中的容器提供默认值。该默认值可以包含可执行文件,也可以省略可执行文件,将CMD指令的参数作为ENTRYPOINT指令的默认参数。
|
||
> 如果CMD指令用于向ENTRYPOINT指令提供默认参数,那么ENTRYPOINT指令和CMD指令都要按照JSON数组的格式进行声明
|
||
|
||
当使用exec form或shell form时,CMD指令制定了镜像运行时默认执行的command。
|
||
如果使用CMD的shell form,那么command将会在`/bin/sh -c`中执行
|
||
```dockerfile
|
||
FROM ubuntu
|
||
CMD echo "This is a test." | wc -
|
||
```
|
||
如果想不在shell中运行CMD,那么必须将command作为JSON ARRAY传递,并且指定可执行文件的full path。
|
||
```dockerfile
|
||
FROM ubuntu
|
||
CMD ["/usr/bin/wc","--help"]
|
||
```
|
||
如果用户在`docker run`命令中指定了参数,那么参数将会覆盖CMD命令提供的默认值。
|
||
## LABEL
|
||
LABEL命令的格式如下所示:
|
||
```dockerfile
|
||
LABEL <key>=<value> <key>=<value> <key>=<value> ...
|
||
```
|
||
LABEL命令向image中添加元数据,一个LABEL是一个key-value对,如果想要在LABEL value中包含空格,需要加入双引号或是转义符:
|
||
```dockerfile
|
||
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命令中指定多个key-value pair。
|
||
**在父镜像中定义的LABEL能被继承,如果父镜像和子镜像中都定义了相同的LABEL,那么最近的LABEL定义将会覆盖父镜像的同名LABEL。
|
||
如果要查看一个镜像的LABEL,可以通过`docker image inspect`命令来进行查看,可以通过`--format`指定显示的格式:
|
||
```shell
|
||
# docker image inspect --format='' myimage
|
||
```
|
||
```json
|
||
{
|
||
"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"
|
||
}
|
||
```
|
||
## EXPOSE
|
||
```dockerfile
|
||
EXPOSE <port> [<port>/<protocol>...]
|
||
```
|
||
EXPOSE指令会告知Docker该container在监听指定的网络端口,可以指定该端口是在监听TCP或是UDP,默认情况下如果没有指定protocol,默认值为TCP。
|
||
EXPOSE指令实际并不开放端口,其作用只是在build image的人和运行container的人之间提供文档,该文档会告知哪些端口需要被开放。
|
||
> 想要真正的开放端口,需要在运行容器时通过docker run命令指定-p选项来开放和映射端口。
|
||
默认情况下,EXPOSE在未指定协议的情况下使用TCP。可以显式指定开放端口的协议为UDP。
|
||
```dockerfile
|
||
EXPOSE 80/udp
|
||
```
|
||
可以通过多行EXPOSE指令同时暴露TCP和UDP端口:
|
||
```dockerfile
|
||
EXPOSE 80/tcp
|
||
EXPOSE 80/udp
|
||
```
|
||
不管EXPOSE命令如何设置,都可以在`docker run`时通过-p选项来覆盖设置:
|
||
```shell
|
||
$ docker run -p 80:80/tcp -p 80:80/udp ...
|
||
```
|
||
## ENV
|
||
ENV指令将环境变量key设置为value,该值可以为后续的指令提供inline替换。
|
||
> 如果双引号没有被转义,那么value中的双引号将会被移除。
|
||
|
||
ENV指令允许一行内设置多条key-value pair。
|
||
```dockerfile
|
||
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
|
||
MY_CAT=fluffy
|
||
```
|
||
通过ENV命令设置的环境变量将会被保留,当container通过resulting image产生时。可以通过docker inspect来查看值,并且通过`docker run --env key=value`的形式来更改。
|
||
> ENV命令设置的key-value对会保留在最终的镜像中,并且在镜像产生的容器中可见。
|
||
|
||
如果环境变量只是在build的过程中需要,并且最终image中不希望存在该环境变量,可以考虑只为单行命令设置value:
|
||
```dockerfile
|
||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ...
|
||
```
|
||
或者可以考虑ARG,ARG不会持久化到最终的image中:
|
||
```dockerfile
|
||
ARG DEBIAN_FRONTEND=noninteractive
|
||
RUN apt-get update && apt-get install -y ...
|
||
```
|
||
# ADD
|
||
ADD指令拥有两种形式:
|
||
```dockerfile
|
||
ADD [--chown=<user>:<group>] [--checksum=<checksum>] <src>... <dest>
|
||
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
|
||
```
|
||
如果路径中包含空格,需要使用第二种形式。
|
||
ADD指令会从`src`拷贝新文件、目录或remote file URL并将其添加到镜像的文件系统中,path为`dest`。
|
||
可以指定复数个`src`资源,但如果`src`为文件或者目录,那么`src`的path将会被解释为相对于build context的路径。
|
||
每个`src`可以含有通配符,示例如下:
|
||
```dockerfile
|
||
ADD hom* /mydir/
|
||
```
|
||
`?`符号可以匹配任意单个字符,示例如下:
|
||
```dockerfile
|
||
ADD hom?.txt /mydir/
|
||
```
|
||
`dest`是一个绝对路径,或是相对于`WORKDIR`的相对路径,`src`将会被复制到目标容器中。
|
||
如果想要ADD包含特殊字符的文件或目录(例如`[`或`]`),需要对这些路径进行转义。
|
||
所有新文件和目录创建时,UID和GID都是0,如果想要为新建文件或目录指定其他的值,可以使用`--chown`选项来指定`username:groupname`或`uid:gid |