- [Dockerfile](#dockerfile) - [format](#format) - [parser directive](#parser-directive) - [syntax](#syntax) - [escape](#escape) - [ENV](#env) - [.dockerignore file](#dockerignore-file) - [FROM](#from) - [ARG和FROM的交互](#arg和from的交互) - [RUN](#run) - [RUN --mount](#run---mount) - [Mount Types](#mount-types) - [RUN --mount=type=bind](#run---mounttypebind) - [RUN --network](#run---network) - [CMD](#cmd) - [LABEL](#label) - [EXPOSE](#expose) - [ENV](#env-1) - [ADD](#add) - [ADD --chown](#add---chown) - [ADD \ \](#add-git-ref-dir) - [COPY](#copy) - [COPY --from](#copy---from) - [ENTRYPOINT](#entrypoint) - [ENTRYPOINT和CMD指令的交互](#entrypoint和cmd指令的交互) - [VOLUME](#volume) - [VOLUME指令要点](#volume指令要点) - [USER](#user) - [WORKDIR](#workdir) - [ARG](#arg) - [default value](#default-value) - [Scope](#scope) - [ENV和ARG使用](#env和arg使用) - [预定义的ARG](#预定义的arg) - [ONBUILD](#onbuild) # 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=] [AS ] ``` or ```dockerfile FROM [--platform=] [:] [AS ] ``` or ```dockerfile FROM [--platform=] [@] [AS ] ``` FROM命令设置了一个初始build状态,并且为其他命令设置了一个初始的镜像。一个有效的dockerfile文件必须以FROM命令开始。 - 在同一个dockerfile中,FROM命令可以出现多次,用于创建多个镜像或将其中一个build stage作为另一个的依赖。只需要在每条新的FROM语句之前记录上一个由commit输出的image id,每条新FROM语句都会清除之前语句创建的所有状态。 - 可以为一个新的build stage指定一个名称,通过在FROM语句之后添加AS NAME,该名称可以在来连续的FROM语句或COPY --from=\中被使用。 - 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 \: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=][,option=[,option=]...] ``` ### 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 = = = ... ``` 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 [/...] ``` 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=:] [--checksum=] ... ADD [--chown=:] ["",... ""] ``` 如果路径中包含空格,需要使用第二种形式。 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包含特殊字符的文件或目录(例如`[`或`]`),需要对这些路径进行转义。 ### ADD --chown 所有新文件和目录创建时,UID和GID都是0,如果想要为新建文件或目录指定其他的值,可以使用`--chown`选项来指定`username:groupname`或`uid:gid. 在仅仅指定了username或UID而没有指定groupname或GID的情况下,将会把GID设置为和UID相同的值。 ADD指令的使用示例如下: ```dockerfile ADD --chown=55:mygroup files* /somedir/ ADD --chown=bin files* /somedir/ ADD --chown=1 files* /somedir/ ADD --chown=10:11 files* /somedir/ ``` - 如果`src`是一个本地archive并且以一种可识别的格式压缩,那么其会被解压为一个目录,从remote获取的资源不会被解压。 - 如果制定了多个`src`,则`dest`必须是目录,并且`dest`必须以`/`结尾。 - 如果`dest`没有指定`/`作为结尾,那么`dest`被视作一个常规文件。 - 如果`dest`不存在,那么其会自动创建路径中所有的缺失目录。 ### ADD \ \ 该形式允许添加一个git仓库到镜像中,而不需要镜像中存在git命令。 ```dockerfile ADD [--keep-git-dir=] ``` `--keep-git-dir`选项代表是否保存git仓库中的.git目录,该选项的默认值是`false`. ## COPY COPY指令可以按如下两种形式编写: ```dockerfile COPY [--chown=:] ... COPY [--chown=:] ["",... ""] ``` 如果路径中含有空格,使用第二种形式。 COPY命令从`src`中复制文件和目录并且将其添加到容器文件系统中,添加路径为`dest`. COPY的使用示例如下所示: ```dockerfile COPY hom* /mydir/ ``` 类似`ADD --chown`,`COPY`命令也支持`COPY --chown`的用法. ### COPY --from COPY支持`--from=`选项,可以将`source`设置为先前的build stage而不是build context。**如果找不到具有相同名称的build stage,则会采用具有相同名称的image**。 ## ENTRYPOINT `ENTRYPOINT`命令具有两种格式: - ***exec***格式: ```dockerfile ENTRYPOINT ["executable", "param1", "param2"] ``` - ***shell***格式: ```dockerfile ENTRYPOINT command param1 param2 ``` ENTRYPOINT允许像一个可执行程序一样配置容器。 例如,如下示例使用默认内容启动了一个nginx实例,监听80端口: ```dockerfile docker run -i -t --rm -p 80:80 nginx ``` `docker run `命令之后的命令行参数将添加到***exec***形式ENTRYPOINT命令的所有元素之后,**并且命令行参数会覆盖CMD指令中指定的元素。**其允许参数被传递给entry point。 例如,`docker run -d`将会把`-d`传递给entry point。 > 可以通过`docker run --entrypoint`命令来覆盖ENTRYPOINT指令。 `shell`格式的ENTRYPOINT将阻止任何`CMD`或`run command line`参数被使用,但是`shell`形式的ENTRYPOINT会作为`/bin/sh -c`的一个子命令启动。 > 如果ENTRYPOINT作为`/bin/sh -c`的一个子命令启动,意味着目标可执行文件并不是容器的`PID 1`,也不会接收Unix信号-可以执行文件不会从docker stop命令中接收到SIGTERM信号。 只有dockerfile中的最后一个ENTRYPOINT命令会起作用。 如果想要在使用ENTRYPOINT `shell form`时保证`PID 1`进程是目标可执行文件,可以在ENTRYPOINT指令前加上`exec`,示例如下所示: ```dockerfile FROM ubuntu ENTRYPOINT exec top -b ``` 如果`shell form`不带`exec`,该命令将作为`/bin/sh -c`的子命令执行,`PID 1`进程为`sh`而不是`top`,调用`docker stop`时容器SIGTERM信号将会被发送给`sh`,然后`top`进程会在超时之后接收到一个SIGKILL信号,并无法干净的退出。 ### ENTRYPOINT和CMD指令的交互 `ENTRYPOINT`指令和`CMD`指令都用于指定容器启动时的命令,它们遵循如下规则: - dockerfile至少应该指定一条ENTRYPOINT或CMD指令 - 当想要将容器像可执行文件一样运行(可以在`docker run`命令后添加传递给`ENTRYPOINT`指令的参数时),应该使用`ENTRYPOINT`指令 - 当为`docker run`命令指定了额外参数时,`CMD`指令中为`ENTRYPOINT`指令指定的默认参数将会被命令行参数覆盖 ## VOLUME ```dockerfile VOLUME ["/data"] ``` `VOLUMN`指令创建了一个具有指定名称的挂载点,并且将其标记为持有外部挂载volume。VOLUMNE指令的值可以通过json array的形式指定(`VOLUME ["/var/log/"]`),也可以通过纯字符串的形式来指定(`VOLUME /var/log`或`VOLUME /var/log /var/db`)。 VOLUMN指令会使用基础image指定目录下的数据来初始化新创建的volume,示例如下: ```dockerfile FROM ubuntu RUN mkdir /myvol RUN Learn more about the "RUN " Dockerfile command. echo "hello world" > /myvol/greeting VOLUME /myvol ``` 在使用`docker run`指令执行上述dockerfile产生镜像时,会在/myvol创建一个挂载点,并且将greeting文件复制到新创建的volume中。 ### VOLUME指令要点 - 如果在VOLUME指令创建volume之后,任何build step修改了volume路径下的数据,那些修改都会被丢弃 ## USER ```dockerfile USER [:] # or USER [:] ``` USER指令用于指定当前stage剩余steps的默认user和group。指定用户用于RUN指令和ENTRYPOINT,CMD命令的执行。 ## WORKDIR ```dockerfile WORKDIR /path/to/workdir ``` WORKDIR用于设置dockerfile中位于该命令之后步骤的工作目录。如果指定的工作目录不存在,那么会创建该工作目录。 在dockerfile中,WORKDIR指令可以使用多次,如果提供了相对路径,相对路径会基于当前工作目录解析: ```dockerfile WORKDIR /a WORKDIR b WORKDIR c RUN pwd ``` 最终,pwd输出的路径是/a/b/c。 WORKDIR指令可以解析环境变量,只可以解析显式在dockerfile中设置的环境变量: ```dockerfile ENV DIRPATH=/path WORKDIR $DIRPATH/$DIRNAME RUN pwd ``` 输出值为`/path/$DIRNAME` > 如果WORKDIR尚未被指定,那么WORKDIR的默认值是`/`。如果父镜像中设置了WORKDIR的值,那么dockerfile将使用父镜像的WORKDIR 为了避免在非预期的目录中执行操作,最好显式在dockerfile中设置WORKDIR. ## ARG ```dockerfile ARG [=] ``` ARG指令可以定义一系列变量,定义的变量在build时可以通过`docker build --build-arg =`来设置值。 如果在`--build-arg`中传递了dockfile中不存在的参数,会抛出警告,表示该变量没有被dockerfile消费。 ```dockerfile FROM busybox ARG user1 ARG buildno # ... ``` ### default value 可以为ARG指定一个默认值,示例如下所示: ```dockerfile FROM busybox ARG user1=someuser ARG buildno=1 # ... ``` 如果在build时没有传递值给ARG变量,那么会使用默认值。 ### Scope 一个ARG指令的作用域范围从定义该ARG行开始算起,一直到当前build stage结束。 ### ENV和ARG使用 当ENV和ARG指令同时被定义时,ENV定义的变量总是会覆盖ARG指令定义的变量。 ### 预定义的ARG dockerfile含有一组预定义的ARG变量,可以直接使用这些预定义的ARG变量而无需在dockerfile中添加ARG指令: - HTTP_PROXY - http_proxy - HTTPS_PROXY - https_proxy - FTP_PROXY - ftp_proxy - NO_PROXY - no_proxy - ALL_PROXY - all_proxy ```shell docker build --build-arg HTTPS_PROXY=https://my-proxy.example.com . ``` ## ONBUILD ```dockerfile ONBUILD ``` ONBUILD指令添加了一个触发器,当该镜像作为其他镜像的基础镜像时,ONBUILD指定的指令将会被执行。ONBUILD触发器的指令将会在下游build操作的context中被执行,就像ONBUILD包含的指令被直接插入到下游FROM指令之后。 使用示例如下: ```dockerfile ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src ```