创建短暂的容器
意思是 container 可以停止和销毁,接着以最小化启动和配置进行重新构建和替换。
理解构建的上下文
使用 docker build ,当前工作环境称为 构建的上下文,默认 Dockerfile 是在同级目录找,可通过 -f 指定 Dockerfile。
无论 Dockerfile 实际在哪里,当前目录的所有递归的文件和目录的内容被发送到 docker daemon 作为构建的上下文。
(无意中包含的不必要文件会增加 image 大小,增加 build/pull/push 时间和 container 运行时大小)
上下文内容支持本地 PATH 和远程 URL,docker build [OPTIONS] PATH | URL | –
17.05开始支持用 stdin 管道化 Dockerfile 的内容
docker build -t foo:v1 . -f-<<<EOF
FROM ubuntu:16.04
RUN echo "hello"
COPY . /copy-files
EOF
使用 .dockerignore 排除内容进入构建上下文
使用多级构建
不需要努力去减少中间层数量和文件,从变化少的层到经常变化的层来排序(这样可以保证复用到构建历史缓存)
不同层的顺序安排:安装工具 -> 安装库依赖 -> 生成应用
不安装不必要的包
解耦应用
每一个 container 应该只关心一件事。解耦应用到多个容器可以让水平扩展更容易和重复使用容器。比如 web 技术栈的分为 应用/数据库/缓存 三个不同的容器。
最小化层的数量
在 17.05 及更高版本中,通过 multi-stage 多级构建减少了这个限制。
给多行参数排序
帮助避免包重复和更容易修改,更易读。
Run apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial
利用构建缓存
如果不想在构建中使用缓存的,给 docker build 命令加 –no-cache=true 选项。
把 update 和 install 放在一行,保证 Dockerfile 安装最近的包版本,如下:
RUN apt-get update && apt-get install -y \
package-foo \
package-bar
APT-GET – 在 apt-get 应用中可能是最常使用的用于 RUN 的命令。因为它可以安装包,RUN apt-get 有几个陷阱需要提防。
不要使用 RUN apt-get dist-upgrade 和 dist-upgrade。因为许多来自父级 image 的基础的包不能在没有权限的 container 中升级。
如果父级 image 中的一个 package 过时了,联系它的维护者。
如果你知道有一个特定的包 foo 需要升级,使用 apt-get install -y foo 来自动更新。
总是把 RUN apt-get update 和 apt-get install 合并为一条 RUN 语句,确保无干涉的安装最新的包版本。
RUN apt-get update && apt-get install -y \
package-foo \
package-bar \
package-baz=1.3.*
&& rm -rf /var/lib/apt/lists/* # 通过移除 apt cache 减小 image 尺寸
单独在一行 RUN 语句中使用 apt-get update 会引起缓存问题,随后的 apt-get install 指令失败,这叫做’ cache busting ’,同样可以通过指定包版本获取 cache-busting,这叫做版本固定,这可以避免由包变化而引起的意外失败。
官方 debian 和 ubuntu 的 image 会自动运行 *apt-get clean*,所以明确调用不是必需的。
使用管道 – 如果想让管道连接的命令在任何阶段遇到错误时就失败,在命令前追加 set -o pipefail && 来保证遇到未期的错误时阻止构建。
例如:RUN set -o pipefail && wget -O – https://some.site | wc -l > /number
不是所有的 shell 都支持 -o pipefail , 基于 debian 的 image 需要指定 bash
例如:RUN [ “/bin/bash”, “-c”, “set -o pipefail && wget -O – https://some.site | wc -l > /number” ]
格式形式为 CMD [“command”, “param1”, “param2”],这个形式的指令推荐用在任何基于服务的 image。
在大多数其他案例中,CMD 应该给一个交互式的shell,比如 CMD [“php”, “-a”],意味着执行 docker run -it php,你会有一个可用的shell。
CMD 很少以 CMD [“param”, “param”] 的方式与 ENTRYPOINT 协同,除非你和用户已经非常熟悉 ENTRYPOINT 是如何工作的。
因此,应该使用常见传统的端口用于应用软件。例如 包含 Apache 的 image 将使用 EXPOSE 80,而包含 MongoDB 的 image 将使用 EXPOSE 27017 等等。
用于外部访问,使用者可以执行 docker run 带上一个标记来标识映射指定端口到他们选择的端口。
对于容器连接,Docker 为从接收容器返回到源的路径提供环境变量。(如,MYSQL_PORT_3306_TCP)
例如:ENV PATH /usr/local/nginx/bin:$PATH 保证 CMD [“nginx”] 能运行。
ENV 指令 在 为你希望容器化的服务提供所需的环境变量 上同样有用,例如 Postgres 的 PGDATA。
每个 ENV 行创建一个新的中间层,就像 RUN 命令。这意味着即使在之后的层 unset 这个环境变量,它仍然存在于这个层,并且它的值可以被打印。测试如下:
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
CMD sh
$ docker run --rm -it test sh echo $ADMIN_USER
要阻止这种情况,真正 unset 环境变量,使用 RUN 指令运行 shell 命令,在一个独立的层中 set, use, unset 变量。
使用 ; 或 && 来分割命令,使用 && 只要一个命令失败,docker build 也失败。
FROM alpine
ENV export ADMIN_USER="mark" \
&& echo $ADMIN_USER > ./mark \
&& unset ADMIN_USER
CMD sh
$ docker run --rm -it test sh echo $ADMIN_USER
因为 COPY 比 ADD 更易懂。COPY 只支持从本地文件到 container 的基本拷贝,而 ADD 有一些不明显的特性(如,本地 tar包 自动解压和支持远程 URL)
因此 ADD 的最佳使用是本地 tar 文件在 image 中的自动解压,如,ADD rootfs.tar.xz / .
如果 Dockerfile 有多个步骤使用了上下文中的不同文件,单独的拷贝它们,而不是一次拷贝所有。这确保每一步的构建缓存在文件发生改变时是失效的,如:
COPY requirements.txt /tmp
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
要让 RUN 步骤导致更少的缓存失效,那么把 COPY . /tmp/ 放到其前面。
因为 image 大小问题,使用 ADD 从远程地址拉取包是极不鼓励的;你应该使用 curl 或 wget 代替。这种方式你可以在解压后把不需要的文件删除,不需要把其它层加到你的 image 中。
避免做的是:
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -zJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
代替的做法是:
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all
对于不需要 ADD 自动解压能力的文件和目录,你应该总是使用 COPY。
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
现在 image 可以像这样显示命令的帮助信息:$ docker run s3cmd
或者使用正确的参数来执行一个命令:$ docker run s3cmd ls s3://mybucket
这是有用的,因为 image 名称可以两次作为到如上命令二进制的引用。
ENTRYPOINT 指令同样可以用来和一个帮助脚本结合,允许它做和命令方式一样的事,尽管需要多进行一步 – 写脚本,以下是 Postgres 官方 image 的 ENTRYPOINT 使用的脚本:
#!/bin/bash
set -e
if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"
if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi
fi
exec "$@"
把帮助脚本拷贝到 container 中,并在 container 启动时通过 ENTRYPOINT 运行:
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]
帮助脚本允许命令有多个参数可以交互,比如:
$ docker run postgres
$ docker run postgres postgres --help
$ docker run --rm -it postgres bash
强烈建议你使用 VOLUMN 在 image 中易变的或用户维护的部分。
通过在 Dockerfile 中创建用户和组开始开始:RUN groupadd -r postgres && useradd –no-log-init -r -g postgres postgres
image 中的用户和组被分配一个非确定的 UID/GID,下次 image 重建时重新分配。所以如果 UID/GID 至关重要,你应该分配一个准确的。
(由于Go archive/tar 包中未解决的bug,在docker 容器中创建相当长的UID会耗尽磁盘,因为容器层中的 /var/log/faillog 填充了NULL字符,权宜措施是传递 –no-log-init 给 useradd)
为了减少层次和复杂性,避免频繁的切换用户。
同样,你应该使用 WORKDIR 而不是 RUN cd … && do-something ,更难度阅读、排除问题和维护。
ONBUILD 在当前 FROM image 派生的任何子 image 中执行。可以认为 ONBUILD 命令是父 Dockerfile 给子 Dockerfile 的指令。
Docker 构建在任何子 Dockerfile 中的命令前执行 ONBUILD。
ONBUILD 对于从给定的 image 构建 image 时有用。例如,你想为一个语言栈的 包含该语言写的任意用户软件到 Dockerfile 中的 image 使用 ONBUILD。
从 ONBUILD 构建的 image 应该有一个分割 tag,例如,ruby:1.9-onbuild 或 ruby-2.0-onbuild
当把 ADD 或 COPY 放到 ONBUILD 时要小心。如果新构建的上下文缺少被添加的资源,onbuild 构建 image 会灾难性失败。通过如上添加分割的 tag 来减轻这种影响。