镜像构建
虽然可以通过 Docker hub 获取到公共镜像,但是针对自己的应用 Docker 化的时候,我们必须要定制镜像了。镜像构建需要引入
Dockerfile
文件,Dockerfile
是一个包含创建镜像所有命令的文本文件,Docker 通过 Dockerfile
的内容来自动构建镜像。docker build
命令构建镜像需要一个 Dockerfile
和一个构建环境(context)。关于构建环境可以是文件系统的具体目录路径也可以是一个 URL,其中 URL 需要是一个 Git 仓库地址。
镜像构建环境是一个递归处理的过程,针对目录来说,则遍历目录下的所有子目录,而 URL 则囊括 Git 仓库本身和它的子模块。镜像构建是通过 Docker daemon 来实现的,而不是客户端。构建开始时,构建进程会把构建环境整个发送给 Docker daemon。假如你的环境是本地文件系统的一个目录,那么尽可能的只包括
Dockerfile
和镜像构建所需要的文件。不要使用 root 目录
/
作为构建环境,否则会发送当前整个文件系统给 Docker daemon。为了提升构建性能,可以通过在当前构建环境根目录下创建
.dockerignore
文件来排除一些不必要的文件和目录(类似 .gitignore
)。可以通过
-f
选项来指定 Dockerfile
,如果不指定则 docker build
默认读取当前名为 Dockerfile 的文件。$ docker build . # 默认读取当前目录下名为 Dockerfile 的文件
$ docker build -f /path/to/a/Dockerfile . # 指定 Dockerfile
通过
-t
可以指定镜像仓库和标签:$ docker build -t shykes/myapp .
-t
选项可以指定多次:$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
在构建过程中,Docker daemon 会逐个运行
Dockerfile
中的指令,在必要时将每条指令的结果提交成为一个新的镜像,并输出新的镜像 ID。Docker daemon 会自动清除发送过去的环境(context)。Docker 中每个指令都是独立的,一条指令创建一个镜像。因为镜像的分层机制,Docker 构建过程中会利用中间镜像(缓存),用来提升构建效率。构建缓存只能用于拥有同一个本地父链(local parent chain)的镜像。意思就是说这些镜像由之前历史构建创建的或者整条镜像链都是由 docker 加载的。如果希望使用特定镜像的构建缓存,则可以使用
--cache-from
选项指定,--cache-from
不需要拥有一个父链并且可以从其它镜像仓库获取。这段描述的有些晦涩,另外
--cache-from
实际过程中应该使用的很少,笔者基本没有这样的应用场景。Dockerfile
的格式是:# Comment 通过 # 号注释
INSTRUCTION arguments
Dockerfile
指令并不区分大小写,但是为了区分,建议指令统一采用 大写
Docker 运行
Dockerfile
指令是顺序执行的,一个 Dockerfile
文件必须以 FROM
指令开始。FROM
指令指定了构建镜像的基础镜像。通过
ENV
可以在 Dockerfile 中声明一个变量,有些指令可以直接通过 $variable_name
或者 ${variable_name}
获取变量(这种方式同 bash 中引用一样)。当然,${variable_name}
还支持标准的 bash
修饰符:${variable:-word}
表示如果variable
有值则使用该值,否则为值word
${variable:+word}
表示如果variable
有值则使用word
,否则为空值
还可以通过
\
转义环境变量: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
RUN
WORKDIR
前面已经提到过
.dockerignore
, 它的功能类似 .gitignore
。它需要存放在构建环境根目录下才会起作用,通过 .dockerignore
定义匹配规则来排除文件和目录。通过 .dockerignore
可以避免不必要的大型或敏感文件和目录发送给 Docker daemon,从而避免 ADD
或者 COPY
. 拷贝这些文件和目录。简单的
.dockerignore
文件如下:# comment
*/temp*
*/*/temp*
temp?
规则 | 解释 |
# comment | 注释,忽略 |
*/temp* | 排除根目录一级子目录下所有以 temp 开头的文件和目录。如 /somedir/temp 、/somedir/temporary.txt 都将会被排除 |
*/*/temp* | 排除根目录下二级子目录下所有以 temp 开头的文件和目录,如 /somedir/subdir/temporary.txt 会被排除 |
temp? | ? 号表示占用一个字符串,如 /tempa 、/tempb 文件目录都会被排除 |
.
├── a # 不匹配规则被保留
│ ├── b # 不匹配规则被保留
│ │ └── tempb # 匹配 */*/temp* 规则被排除
│ └── tempa # 匹配 */temp* 规则被排除
├── temp # 不匹配规则被保留
└── tempc # 匹配规则 temp? 被排除
.dockerignore
的匹配规则遵循 Go 的 filepath.Match 规则。除了该规则外,Docker 还支持了一些特殊的通配符,**
匹配任意层级的目录。例如,**/*.go
将排除构建环境根目录下所有以 .go
为后缀的文件。!
表示忽略排除,如下:*.md
!README.md
表示排除根目录当前层级除了
README.md
外所有以 .md
为后缀的文件。.
├── README.md # 匹配规则被保留
├── a.md # 匹配规则被排除
└── temp # 不匹配规则被保留
└── t.md # 不匹配规则被保留
匹配是有顺序的,如果前后的规则有重叠或者冲突,则后面的规则生效。如果
!README.md
在 *.md
之前,则以 *.md
为规则,README.md
依然会被排除。可以通过
.dockerignore
来排除 Dockerfile
和 .dockerignore
文件。但是这些文件依然会发送到 Docker daemon。不过,ADD
和 COPY
指令将不会拷贝它们。