Docker可以通过Dockerfile中的指令,自动的构建镜像.
Dockerfile又是什么?
一个Dockerfile就是一个文本文档,在这文档内,包含了用户可以在命令行上调用的,用来编排容器的所有指令.
可以使用:
docker build 命令
它会连续的执行Dockerfile中的一些指令,来自动的为我们构建镜像.
docker build命令,可以通过Dockerfile及其上下文环境context
来构建一个镜像.
所谓的context
,所指的是,用来构建镜像的一系列文件所放置的PATH
或者是URL
.
PATH:指的是本地文件系统中的目录.
URL:指的是Git仓库的地址.
context中的结构会被递归访问,也就是说
PATH及其所包含的所有子目录.
# and
URL所指向的仓库及其子模块.
假设当前目录为context的话,那么编译命令为:
sudo docker build .
build命令是动过Docker 后台进程(daemon)来执行的,并非shell的命令行.
build首先要做的就是讲整个context
内容发送到daemon.
在大多情况下,最好创建一个空的目录来进行操作,将你的Dockerfile放到该目录,再将需要的内容添加进来.
不要用根目录,/作为PATH,因为这会导致,build会将整个硬盘的内容都加载到Docker daemon
如果想要在build的context中使用某个文件,需要使用诸如COPY
等指令将文件添加上下文.
为了提高build时的性能,很多不需要被打包进镜像的目录或文件可以写入.dockerignore
文件中,.dockerignore文件本身也不会被打包进镜像.
Dockerfile
一般都命名为Dockerfile,并且被放在context的根,但是也可以通过使用-f
选项,来指定放在任意目录的Dockerfile
。
sudo docker build -f /path/to/a/Dockerfile .
你也可以指定一个仓库和标签,来存放build成功后生成的镜像.(也可以使用多个-t选项,来指定多个仓库存放生成的镜像)
docker build -t REPOSITORY/TAG.
Docker daemon在执行Dockerfile中的指令之前,会先对语法进行初步校验,如果有语法错误它会将错误信息反馈给我们.
Docker daemon会逐行执行Dockerfile中的指令,在最终生成新的镜像ID之前,如果有必要它会将每一条指令的执行结果都提交成一层新的镜像.最后Docker daemon会自动清理添加到context中的内容.
值得注意的是,每条指令都是独立运行并且会产生一层新的镜像,因此,RUN cd /tmp
对它下一条指令不会有任何影响,即不会切换到/tmp目录中.
自18.09版本开始,Docker支持moby/buildkit
项目所提供的一种新的后台来执行镜像的构建——BuildKit
与老版本相比它有很多优势:
1. Detect and skip executing unused build stages
检测并且跳过无用的步骤
2. Parallelize building independent build stages
可以并行执行彼此独立的步骤
3. Incrementally transfer only the changed files in your build context between builds
在多次build之间,只将有改动的文件添加到context
4. Detect and skip transferring unused files in your build context
检测并跳过向context中添加未被使用的文件
5. Use external Dockerfile implementations with many new features
使用外部Dockerfile实现许多新的特性
6. Avoid side-effects with rest of the API (intermediate images and containers)
避免了中间镜像和容器的API所产生的不良影响
7. Prioritize your build cache for automatic pruning
优先使用build cache来实现自动优化
如果想要使用BuildKit
后台,你需要在使用docker build
之前,先在命令行设置环境变量:
DOCKER_BUILDKIT=1
更多关于BuildKit的内容可以参见:
https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
# 注释
# 指令 参数
INSTRUCTION arguments
指令并不区分大小写,但是国际惯例是将指令大写,毕竟这样有助于我们清晰的辨别哪些是指令,而哪些又是参数.
一个Dockerfile必须以FROM
指令作为指令的开头.
只有
parser directives
# comment
),FROM
之前.FROM
指令用来指定基础镜像,即你的新镜像必须在一个基础镜像之上构建.
在Dockerfile中,Parser directives是非必须的,它的作用是影响位于它后续的指令,并且它不会产生一个新的镜像层,也不会再build步骤中显示信息.Parser directives的写法可以看做是特殊的注释形式:
# directive=value
它仅能被使用一次,(仅能够出现一次
)而且,一旦当一行注释,空行,或其他指令被访问,Docker就不在认Parser directives,
而仅仅当作是注释,因此Parser directives必须被写在Dockerfile的开头.
Parser directives不区分大小写.然而,国际惯例是小写的,并且依照国际惯例,在Parser directives之后应该空一行,再开始写其他内容.Parser directives也不支持类似shell的\
符号.(即续行符号)
下面演示一些无效的Parser directives示例
续行符无效:
# direc \
tive=value
出现2次,所以无效:
这里解释一下,因为Parser directives在一个Dockerfile中只能出现一次,而第二次出现后被视作为注释,一旦这行注释生效了,Parser directives也就失效了,后边一律不认.
# directive=value1
# directive=value2
由于出现在FROM
指令之后,所以被当做注释
FROM ImageName
# directive=value
在注释之后出现,也被当做注释
# About my dockerfile
# directive=value
由于解释器并不能识别,unknowndirective
,所以它被当做注释,而出现在注释之后的Parser directives你懂得.
# unknowndirective=value
# knowndirective=value
非换行空白符是允许的,因此下面的写法都是一样的.(这些写法都可以当做Parser directives)
#directive=value
# directive =value
# directive= value
# directive = value
# dIrEcTiVe=value
只有当BuildKit backend
启用时,才支持syntax directive
# syntax=[remote image reference]
比如:
# syntax=docker/dockerfile
# syntax=docker/dockerfile:1.0
# syntax=docker.io/docker/dockerfile:1
# syntax=docker/dockerfile:1.0.0-experimental
# syntax=example.com/user/repo:tag@sha256:abcdef...
(你要先有一个builder才能执行build命令不是吗)
syntax directive声明要build当前的Dockerfile时所使用的builder位置.
它可以无缝的将外部的builder发布成Docker镜像,然后在容器沙河环境来执行.
自定义的Dockerfile实现可以让你:
Automatically get bugfixes without updating the daemon
在不用更新daemon(Docker)的情况下自动获取bug修复.
Make sure all users are using the same implementation to build your Dockerfile
可以确保所有用户都用相同的实现方式来build你们的Dockerfile.(比如生产中N个开发组,甚至N个公司协作开发的情况下,那么docker版本,OS环境等不同因素所可能会导致的build结果的差异.)
Use the latest features without updating the daemon
在不更新daemon的情况下使用最新的特性.
Try out new experimental or third-party features
可以尝试最新的(实验阶段的)特性,及第三方的特性.
更多信息请参见:
https://docs.docker.com/engine/reference/builder/#syntax
作用:指定在Dockerfile的转义符号,默认是\
# escape=\ (backslash)
Or
# escape=` (backtick)
转义符被用来:
要注意的是,转义符不能用于RUN
命令,除非在一行结尾处使用.
将转义符设置为 ` ,的原因,是因为在windows中,\ 是路径分隔符.
环境变量使用ENV
语句声明,它也可以被用于特定的Dockerfile指令中.
Environment variables are notated in the Dockerfile either with $variable_name or ${variable_name}. They are treated equivalently and the brace syntax is typically used to address issues with variable names with no whitespace, like ${foo}_bar.
在Dockerfile中,环境变量的用法跟shell中类似,使用:
$variable_name
or
${variable_name}
它们是等效的,而后者经常被用于解决变量名之间没有空格,如变量拼写:
${foo}_bar
转义符也可以用在环境变量前,例如:
\$foo
# 和
\${foo}
会分别被转义成文字.而非当做变量.下面看个例子:
首先必须要吐槽一下,这个例子坑了我大概有1个小时,可能是官方的文档很久没有更新了,如果你将下列的内容直接复制到Dockerfile中,然后build,那么毫无疑问,在ADD和COPY指令处,一定会报错.
FROM busybox
ENV foo /bar
WORKDIR ${foo} # WORKDIR /bar
ADD . $foo # ADD . /bar
COPY \$foo /quux # COPY $foo /quux
首先对指令进行一个解释:
FROM busybox
基于busybox镜像制作新的镜像
ENV foo /bar
设置环境变量foo,赋值为/bar
WORKDIR ${foo}
切换进入容器后的工作目录到/bar(也就是说进入到容器时你就处于/bar目录),没有的话会自动创建
ADD . $foo
将本`地当前目录`中的内容添加到,目标镜像的/bar目录
COPY \$foo /quux
将本地名为$foo的文件,赋值到目标镜像的/quux目录中,没有的话会自动创建
坑1
:指令后的 # WORKDIR /bar 等等信息看似注释,但是实际上#
如果不是这一行第一个字符(空格除外),那么他就不是注释,所以指令一定走不通.
坑2
:\$foo
其实指的是你需要在与Dockerfile同级处,创建一个名字为$foo
的文件,否则COPY一定会报错.
touch \$foo
网上千篇一律的复制粘贴,估计从来都没自己编译过这个镜像,所以碰到同样错误的同学要注意了.真的很坑啊.好歹也是官方文档.
${variable_name}的写法也支持一些bash的符号:
${variable:-word}
${variable:+word}
在Dockerfile中,下面的指令也支持环境变量:
ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
以及
在1.4版本之后的,ONBUILD
指令.
环境变量的赋值替换在指令结束之前,并不会被替换为新的值.举例来说:
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
def
的值是hello而不是bye,因为第二行是在同一个指令中,ghi的值是bye.
在将context
送到Docker daemon之前,docker会先在context
根目录中,找一个名为.dockerignore
的文件,如果这个文件存在,则这个文件中所匹配的内容将不会被添加到context
中.
这么做可以避免传送不必要的大文件或目录到daemon,也避免了它们会被COPY
,ADD
等指令添加到镜像中.
For example, the patterns /foo/bar and foo/bar both exclude a file or directory named bar in the foo subdirectory of PATH or in the root of the git repository located at URL. Neither excludes anything else.
命令行解释器,将.dockerignore
文件逐行进行匹配,匹配方式类似Unix shell的通配符.
处于匹配的目的,context
的根路径被当做根路径及当前工作路径.
比如,/foo/bar
和foo/bar
都表示,在PATH或URL的foo
子目录中,的bar
都会被排除在context之外.
更多内容请参见:
https://docs.docker.com/engine/reference/builder/#dockerignore-file