Docker 官方镜像是托管在 Docker Hub 上的精选镜像。主要原则是:
专注于免费和开源软件
支持多种架构
举例说明Dockerfile
最佳实践
积极重建更新和安全修复
遵守上游建议
在合适的情况下为容器环境添加最低限度的生活质量行为
请参阅 Docker 的文档以获取该程序的高级概述。
本质上,我们努力听取上游关于他们打算如何使用其软件的建议。许多图像如果不是由相关上游项目直接维护的话,也是与相关上游项目合作维护的。此外,我们的目标是举例说明 Dockerfile 的最佳实践,以便在您制作或派生自己的镜像时作为参考。
(如果您是存在镜像的上游代表并且您想参与其中,请参阅下面的维护部分!)
一些镜像已移植到其他架构,其中许多镜像得到了官方支持(不同程度)。
arm32v6
):https://hub.docker.com/u/arm32v6/arm32v7
):https://hub.docker.com/u/arm32v7/arm64v8
):https://hub.docker.com/u/arm64v8/amd64
):https://hub.docker.com/u/amd64/windows-amd64
): https://hub.docker.com/u/winamd64/arm32v5
):https://hub.docker.com/u/arm32v5/ppc64le
):https://hub.docker.com/u/ppc64le/s390x
):https://hub.docker.com/u/s390x/mips64le
):https://hub.docker.com/u/mips64le/riscv64
):https://hub.docker.com/u/riscv64/i386
):https://hub.docker.com/u/i386/截至 2017 年 9 月 12 日,这些其他架构通过“清单列表”(在 OCI 映像规范中也称为“索引”)包含在无前缀映像下,例如docker run hello-world
应该在所有支持的平台上按原样运行。
如果您对它们是如何构建的感到好奇,请访问 https://doi-janky.infosiftr.net/job/multiarch/ 查看构建脚手架。
请参阅下面的多架构部分,了解向官方映像添加更多架构的建议。
是的!我们有一个专门的常见问题解答存储库,我们尝试在其中收集其他常见问题(关于该计划和我们的实践)。
感谢您对 Docker 官方镜像项目的兴趣!我们努力使这些说明尽可能简单明了,但如果您发现自己迷失了,请随时在#docker-library
频道中的 Libera.Chat IRC 上寻找我们,或者在此处创建 GitHub 问题。
请务必熟悉 Docker Hub 上的官方存储库以及 Docker 文档中编写 Dockerfile 的最佳实践。这些将成为官方图像维护人员执行审查过程的基础。如果您希望审核过程更加顺利,请在提交拉取请求之前确保您的Dockerfile
遵守此处以及下文中提到的所有要点。
此外,这些镜像的 Hub 描述目前单独存储在docker-library/docs
存储库中,其README.md
文件详细说明了其结构以及如何对其做出贡献。请准备好在那里提交 PR,等待您的图片在这里被接受。
由于官方镜像旨在成为 Docker 新手的学习工具,以及高级用户构建生产版本的基础镜像,因此我们会审查每个提议的Dockerfile
,以确保其满足质量和可维护性的最低标准。虽然其中一些标准很难定义(由于主观性),但这里尽可能多地定义,同时在适当的情况下遵循“最佳实践”。
维护者在审查期间可以使用的清单可以在NEW-IMAGE-CHECKLIST.md
中找到。
应及时关注版本更新和安全修复。
如果您不代表上游,而上游对维护映像感兴趣,则应采取措施确保映像维护权顺利过渡到上游。
对于有兴趣接管现有存储库维护权的上游,第一步是参与现有存储库。对问题发表评论、提出更改以及在“图像社区”中让自己出名(即使该“社区”只是当前的维护者)都是重要的起点,以确保现有贡献者和用户不会对过渡感到意外。
接管现有存储库时,请确保原始存储库的整个 Git 历史记录都保存在新的上游维护的存储库中,以确保审核过程在过渡期间不会停止。这最容易通过从现有存储库中分叉新的来完成,但也可以通过直接从原始存储库中获取提交并将其推送到新存储库中来完成(即git fetch https://github.com/jsmith/example.git master
、 git rebase FETCH_HEAD
、 git push -f
)。在 GitHub 上,另一种方法是转移 git 存储库的所有权。这可以在不授予任一组管理员访问其他所有者存储库的情况下完成:
重建相同的Dockerfile
应该会导致打包相同版本的镜像,即使第二个构建发生在几个版本之后,或者构建应该彻底失败,这样标记为0.1.0
的Dockerfile
的无意重建就不会结束包含0.2.3
。例如,如果使用apt
安装映像的主程序,请确保将其固定到特定版本(例如: ... apt-get install -y my-package=0.1.0 ...
)。对于apt
安装的依赖包,通常不需要将它们固定到某个版本。
任何官方镜像都不能源自或依赖于非官方镜像(允许非镜像scratch
和在.external-pins
中固定的有意限制的例外情况——另请参阅.external-pins/list.sh
)。
所有官方镜像都应提供一致的界面。初级用户应该能够docker run official-image bash
(或sh
),而无需了解--entrypoint
。对于高级用户来说,利用入口点也很好,这样他们就可以docker run official-image --arg1 --arg2
而无需指定要执行的二进制文件。
如果启动过程不需要参数,只需使用CMD
:
CMD [ "irb" ]
如果需要在启动时完成初始化(例如创建初始数据库),请结合使用ENTRYPOINT
和CMD
:
ENTRYPOINT [ "/docker-entrypoint.sh" ]
CMD [ "postgres" ]
确保docker run official-image bash
(或sh
)也能正常工作。最简单的方法是检查预期的命令,如果是其他命令,只需exec "$@"
(运行传递的任何内容,正确保持参数转义)。
#! /bin/sh
set -e
# this if will check if the first argument is a flag
# but only works if all arguments require a hyphenated flag
# -v; -SL; -f arg; etc will work, but not arg1 arg2
if [ " $# " -eq 0 ] || [ " ${1 # -} " != " $1 " ] ; then
set -- mongod " $@ "
fi
# check for the expected command
if [ " $1 " = ' mongod ' ] ; then
# init db stuff....
# use gosu (or su-exec) to drop to a non-root user
exec gosu mongod " $@ "
fi
# else default to run whatever the user wanted like "bash" or "sh"
exec " $@ "
如果映像仅包含主可执行文件及其链接库(即没有 shell),那么可以使用可执行文件作为ENTRYPOINT
,因为这是唯一可以运行的东西:
ENTRYPOINT [ "fully-static-binary" ]
CMD [ "--help" ]
判断这是否合适的最常见指标是镜像Dockerfile
scratch
开始( FROM scratch
)。
尝试使Dockerfile
易于理解/阅读。为了简洁起见,将复杂的初始化细节放入独立脚本中并仅在Dockerfile
中添加RUN
命令可能很诱人。然而,这会导致生成的Dockerfile
过于不透明,这样的Dockerfile
不太可能通过审核。相反,建议将所有初始化命令作为适当的RUN
或ENV
命令组合放入Dockerfile
中。要找到好的例子,请查看当前的官方图片。
在撰写本文时的一些示例:
遵循 Docker 指南,强烈建议每个容器生成的镜像仅包含一个关注点;这主要意味着每个容器只有一个进程,因此不需要完整的初始化系统。在两种情况下,类似 init 的进程会对容器有所帮助。第一个是信号处理。如果启动的进程没有通过退出来处理SIGTERM
,则它不会被杀死,因为它在容器中的 PID 为 1(请参阅 docker 文档中前台部分末尾的“注意”)。第二种情况就是僵尸收割。如果进程生成子进程并且没有正确获取它们,则会导致进程表已满,这可能会阻止整个系统生成任何新进程。对于这两个问题,我们推荐 tini。它非常小,具有最小的外部依赖性,填补了这些角色中的每一个,并且只执行收割和信号转发的必要部分。
请务必根据需要在CMD
或ENTRYPOINT
中使用 tini。
最好从发行版提供的软件包(例如apt-get install tini
)安装 tini 。如果 tini 在您的发行版中不可用或者太旧,以下是要添加到 tini 中的Dockerfile
片段:
# Install tini for signal processing and zombie killing
ENV TINI_VERSION v0.18.0
ENV TINI_SIGN_KEY 595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7
RUN set -eux;
wget -O /usr/local/bin/tini "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini" ;
wget -O /usr/local/bin/tini.asc "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini.asc" ;
export GNUPGHOME= "$(mktemp -d)" ;
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$TINI_SIGN_KEY" ;
gpg --batch --verify /usr/local/bin/tini.asc /usr/local/bin/tini;
command -v gpgconf && gpgconf --kill all || :;
rm -r "$GNUPGHOME" /usr/local/bin/tini.asc;
chmod +x /usr/local/bin/tini;
tini --version
在这一点上,经验最终胜过文档,成为启蒙之路,但以下提示可能会有所帮助:
尽可能避免COPY
/ ADD
,但在必要时,尽可能具体(即COPY one-file.sh /somewhere/
而不是COPY . /somewhere
)。
原因是COPY
指令的缓存将文件mtime
更改视为缓存失效,这有时会使COPY
的缓存行为变得不可预测,特别是当.git
是需要COPY
的一部分时(例如)。
确保不太可能发生变化的行出现在更有可能发生变化的行之前(需要注意的是,每行都应该生成一个在不假设后面的行的情况下仍能成功运行的图像)。
例如,包含软件版本号 ( ENV MYSOFTWARE_VERSION 4.2
) 的行应位于设置 APT 存储库.list
文件的行 ( RUN echo 'deb http://example.com/mysoftware/debian some-suite main' > /etc/apt/sources.list.d/mysoftware.list
)。
Dockerfile
编写应有助于减轻构建期间的拦截攻击。我们的要求集中在三个主要目标:验证来源、验证作者和验证内容;这些分别通过以下方式完成: 尽可能使用 https;在Dockerfile
中导入具有完整指纹的 PGP 密钥以检查签名;将校验和直接嵌入到Dockerfile
中。应尽可能使用这三种方法。当没有发布签名时,只能使用 https 和嵌入的校验和。作为最后的手段,如果网站没有可用的 https 且没有签名,则只接受嵌入的校验和。
推荐使用 https 来下载所需工件的目的是确保下载来自受信任的来源,而这也恰好使拦截变得更加困难。
推荐 PGP 签名验证的目的是确保只有授权用户才能发布给定的工件。导入 PGP 密钥时,请尽可能使用keys.openpgp.org
服务(否则最好使用keyserver.ubuntu.com
)。另请参阅有关密钥和验证的常见问题解答部分。
建议进行校验和验证的目的是验证工件是否符合预期。这确保了当远程内容更改时,Dockerfile 也会更改并提供自然的docker build
缓存清理。作为一个额外的好处,这还可以防止在版本不佳的文件上意外下载比预期更新的工件。
以下是一些示例:
首选:通过 https 下载,PGP 密钥完整指纹导入和asc
验证,嵌入式校验和验证。
ENV PYTHON_DOWNLOAD_SHA512 (sha512-value-here)
RUN set -eux;
curl -fL "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz" -o python.tar.xz;
curl -fL "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz.asc" -o python.tar.xz.asc;
export GNUPGHOME= "$(mktemp -d)" ;
# gpg: key F73C700D: public key "Larry Hastings <[email protected]>" imported
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 97FC712E4C024BBEA48A61ED3A5CA953F73C700D;
gpg --batch --verify python.tar.xz.asc python.tar.xz;
rm -r "$GNUPGHOME" python.tar.xz.asc;
echo "$PYTHON_DOWNLOAD_SHA512 *python.tar.xz" | sha512sum --strict --check;
# install
备用:将完整密钥指纹导入到 apt 中,它将在下载和安装软件包时检查签名和校验和。
RUN set -eux;
key= 'A4A9406876FCBD3C456770C88C718D3B5072E1F5' ;
export GNUPGHOME= "$(mktemp -d)" ;
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ;
gpg --batch --armor --export "$key" > /etc/apt/trusted.gpg.d/mysql.gpg.asc;
gpgconf --kill all;
rm -rf "$GNUPGHOME" ;
apt-key list > /dev/null
RUN set -eux;
echo "deb http://repo.mysql.com/apt/debian/ bookworm mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list;
apt-get update;
apt-get install -y mysql-community-client= "${MYSQL_VERSION}" mysql-community-server-core= "${MYSQL_VERSION}" ;
rm -rf /var/lib/apt/lists/*;
# ...
(顺便说一句, rm -rf /var/lib/apt/lists/*
与apt-get update
大致相反——它确保该层不包含额外的约 8MB 的 APT 包列表数据,并且强制执行适当的apt-get update
使用。)
安全性较低的替代方案:将校验和嵌入到Dockerfile
中。
ENV RUBY_DOWNLOAD_SHA256 (sha256-value-here)
RUN set -eux;
curl -fL -o ruby.tar.gz "https://cache.ruby-lang.org/pub/ruby/$RUBY_MAJOR/ruby-$RUBY_VERSION.tar.gz" ;
echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.gz" | sha256sum --strict --check;
# install
注意:使用 SHA1 或 MD5 应被视为“最后手段的校验和”,因为两者通常被认为是不安全的:
不可接受:通过 http(s) 下载文件而不进行验证。
RUN curl -fL "https://julialang.s3.amazonaws.com/bin/linux/x64/${JULIA_VERSION%[.-]*}/julia-${JULIA_VERSION}-linux-x86_64.tar.gz" | tar ...
# install
默认情况下,Docker 容器以较低的权限执行:白名单 Linux 功能、控制组和默认 Seccomp 配置文件(1.10+ 带主机支持)。在容器中运行的软件可能需要额外的权限才能正常运行,并且有许多命令行选项可以自定义容器执行。请参阅docker run
Reference 和 Seccomp for Docker 以供参考。
需要额外权限的官方存储库应指定软件运行的最小命令行选项集,如果这引入了重大的可移植性或安全问题,则仍可能被拒绝。一般来说,不允许使用--privileged
,但--cap-add
和--device
选项的组合可能是可以接受的。此外, --volume
可能会很棘手,因为有许多主机文件系统位置会引入可移植性/安全性问题(例如 X11 套接字)。
对于构成安全修复的映像更新,我们建议采取一些措施来帮助确保尽快合并、构建和发布您的更新:
[email protected]
发送电子邮件,以便我们提前了解并估算时间(以便我们可以适当安排传入更新的时间)。[security]
(例如, [security] Update FooBar to 1.2.5, 1.3.7, 2.0.1
)。每个存储库可以为任何和所有标签指定多个架构。如果未指定体系结构,则映像将在amd64
(又名 x86-64)上的 Linux 中构建。要指定更多或不同的体系结构,请使用Architectures
字段(逗号分隔列表,删除空格)。有效的架构可以在 Bashbrew 的oci-platform.go
文件中找到:
amd64
arm32v6
arm32v7
arm64v8
i386
mips64le
ppc64le
riscv64
s390x
windows-amd64
任何给定标签的Architectures
必须是其FROM
标签的Architectures
的严格子集。
镜像库文件中的每个条目必须有一个可用于多种架构的Dockerfile
。这意味着每个受支持的体系结构将具有相同的FROM
行(例如FROM debian:bookworm
)。请参阅golang
、 docker
、 haproxy
和php
以获取每个条目使用一个Dockerfile
的库文件示例,并查看它们各自的 git 存储库以获取示例Dockerfile
。
如果 Dockerfile 的不同部分仅发生在一种架构或另一种架构中,请使用控制流(例如if
/ case
)以及dpkg --print-architecture
或apk -print-arch
来检测用户空间架构。仅当无法安装更准确的工具时才使用uname
进行体系结构检测。请参阅 golang 的示例,其中某些架构需要从上游源包构建二进制文件,而某些架构只需下载二进制版本。
对于像debian
这样的基础镜像,需要有一个不同的Dockerfile
和构建上下文,以便ADD
特定于架构的二进制文件,这是上述情况的一个有效例外。由于这些图像使用相同的Tags
,因此它们需要位于同一条目中。使用GitRepo
、 GitFetch
、 GitCommit
和Directory
的体系结构特定字段,这些字段是用连字符 ( -
) 和字段连接的体系结构(例如arm32v7-GitCommit
)。任何没有特定于体系结构字段的体系结构都将使用默认字段(例如,没有arm32v7-Directory
意味着Directory
将用于arm32v7
)。有关示例,请参阅库中的debian
或ubuntu
文件。以下是hello-world
的示例:
Maintainers: Tianon Gravi <[email protected]> (@tianon),
Joseph Ferguson <[email protected]> (@yosifkit)
GitRepo: https://github.com/docker-library/hello-world.git
GitCommit: 7d0ee592e4ed60e2da9d59331e16ecdcadc1ed87
Tags: latest
Architectures: amd64, arm32v5, arm32v7, arm64v8, ppc64le, s390x
# all the same commit; easy for us to generate this way since they could be different
amd64-GitCommit: 7d0ee592e4ed60e2da9d59331e16ecdcadc1ed87
amd64-Directory: amd64/hello-world
arm32v5-GitCommit: 7d0ee592e4ed60e2da9d59331e16ecdcadc1ed87
arm32v5-Directory: arm32v5/hello-world
arm32v7-GitCommit: 7d0ee592e4ed60e2da9d59331e16ecdcadc1ed87
arm32v7-Directory: arm32v7/hello-world
arm64v8-GitCommit: 7d0ee592e4ed60e2da9d59331e16ecdcadc1ed87
arm64v8-Directory: arm64v8/hello-world
ppc64le-GitCommit: 7d0ee592e4ed60e2da9d59331e16ecdcadc1ed87
ppc64le-Directory: ppc64le/hello-world
s390x-GitCommit: 7d0ee592e4ed60e2da9d59331e16ecdcadc1ed87
s390x-Directory: s390x/hello-world
Tags: nanoserver
Architectures: windows-amd64
# if there is only one architecture, you can use the unprefixed fields
Directory: amd64/hello-world/nanoserver
# or use the prefixed versions
windows-amd64-GitCommit: 7d0ee592e4ed60e2da9d59331e16ecdcadc1ed87
Constraints: nanoserver
有关库文件格式的更多信息,请参阅指令格式部分。
提出新的官方形象不应轻易进行。我们期望并要求您承诺维护您的形象(包括特别是适时的及时更新,如上所述)。
库定义文件是在official-images
存储库的library/
目录中找到的纯文本文件。每个库文件控制 Docker Hub 描述中显示的当前“受支持”的图像标签集。从库文件中删除的标签不会从 Docker Hub 中删除,因此旧版本可以继续使用,但不会由上游或官方镜像的维护者维护。库文件中的标签仅通过更新该库文件或更新其基础镜像来构建(即,构建FROM debian:bookworm
debian:bookworm
的镜像)。当库有更新时,只有库文件中的内容才会被重建。
鉴于此政策,有必要澄清一些情况:回填版本、候选版本和持续集成构建。当提出新的存储库时,通常会在初始拉取请求中包含一些不受支持的旧版本,并同意在接受后立即删除它们。不要将其与综合历史档案混淆,这不是本意。术语“支持”有点延伸的另一个常见情况是候选版本。候选版本实际上只是预期寿命较短的版本的命名约定,因此它们是完全可以接受和鼓励的。与发布候选版本不同,持续集成构建具有基于代码提交或定期计划的完全自动化的发布周期,这是不合适的。
强烈建议您在创建新的library/
文件内容(以及历史记录,以了解它们如何随着时间的推移而变化),以熟悉流行的约定并进一步帮助简化审核过程(因此我们可以专注于内容而不是深奥的格式或标签使用/命名)。
定义文件的文件名将决定它在 Docker Hub 上创建的映像存储库的名称。例如, library/ubuntu
文件将在ubuntu
存储库中创建标签。
存储库的标签应反映上游的版本或变体。例如,Ubuntu 14.04 也称为 Ubuntu Trusty Tahr,但通常简称为 Ubuntu Trusty(尤其是在使用中),因此ubuntu:14.04
(版本号)和ubuntu:trusty
(版本名称)是相同映像内容的适当别名。在 Docker 中, latest
标签是一个特例,但它有点用词不当; latest
确实是“默认”标签。当执行docker run xyz
时,Docker 会将其解释为docker run xyz:latest
。考虑到这一背景,没有其他标签包含字符串latest
,因为它不是用户期望或鼓励实际键入的内容(即, xyz:latest
实际上应该简单地用作xyz
)。换句话说,“XYZ 的最高 2.2 系列版本”的别名应该是xyz:2.2
,而不是xyz:2.2-latest
。同样,如果xyz:latest
存在 Alpine 变体,则应将其别名为xyz:alpine
,而不是xyz:alpine-latest
或xyz:latest-alpine
。
强烈建议为版本号标签指定别名,以便用户轻松保留特定系列的“最新”版本。例如,鉴于当前支持的 XYZ 软件版本为 2.3.7 和 2.2.4,建议的别名将分别为Tags: 2.3.7, 2.3, 2, latest
和Tags: 2.2.4, 2.2
。在此示例中,用户可以使用xyz:2.2
轻松使用 2.2 系列的最新补丁版本,如果需要较小的粒度,则可以使用xyz:2
(Python 是一个很好的示例,说明了它最明显有用的地方 - python:2
和python:3
非常不同,可以被认为是 Python 每个主要版本轨道的latest
标签)。
如上所述, latest
实际上是“默认”的,因此,如果用户不知道或不关心他们使用哪个版本,则它作为别名的映像应该反映用户应该使用哪个版本或软件变体。以 Ubuntu 为例, ubuntu:latest
指向最新的 LTS 版本,因为如果大多数用户知道自己想要 Ubuntu 但不知道或不关心哪个版本(特别是考虑到它将是在任何给定时间最“稳定”且得到良好支持的版本)。
清单文件格式正式基于 RFC 2822,因此对于已经熟悉许多流行互联网协议/格式(例如 HTTP 或电子邮件)“标头”的人来说应该很熟悉。
主要添加内容的灵感来自 Debian 通常使用 2822 的方式——即,以#
开头的行被忽略,“条目”由空行分隔。
第一个条目是图像的“全局”元数据。全局条目中唯一必填的字段是Maintainers
,其值以逗号分隔,格式为Name <email> (@github)
或Name (@github)
。全局条目中指定的任何字段都将成为其余条目的默认字段,并且可以在单个条目中覆盖。
# this is a comment and will be ignored
Maintainers: John Smith <[email protected]> (@example-jsmith),
Anne Smith <[email protected]> (@example-asmith)
GitRepo: https://github.com/example/docker-example.git
GitCommit: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
# this is also a comment, and will also be ignored
Tags: 1.2.3, 1.2, 1, latest
Directory: 1
Tags: 2.0-rc1, 2.0-rc, 2-rc, rc
GitRepo: https://github.com/example/docker-example-rc.git
GitFetch: refs/heads/2.0-pre-release
GitCommit: beefdeadbeefdeadbeefdeadbeefdeadbeefdead
Directory: 2
File: Dockerfile-to-use
Bashbrew 会在指定的提交 ( GitCommit
) 处从 Git 存储库 ( GitRepo
) 中获取代码。如果通过获取关联的GitRepo
的master
无法获得所引用的提交,则有必要为GitFetch
提供一个值,以便告诉 Bashbrew 要获取哪些引用以获得必要的提交。
构建的镜像将被标记为<manifest-filename>:<tag>
(即Tags
值为1.6, 1, latest
library/golang
将创建golang:1.6
、 golang:1
和golang:latest
标签)。
或者,如果Directory
存在,Bashbrew 将在指定的子目录中而不是在根目录中查找Dockerfile
(并且Directory
将用作构建的“上下文”而不是存储库的顶层)。如果存在File
,则将使用指定的文件名而不是Dockerfile
。
有关如何为特定架构指定不同的GitRepo
、 GitFetch
、 GitCommit
或Directory
的详细信息,请参阅多架构部分。
library/
文件夹中创建一个新文件。它的名称将是您在 Hub 上的存储库的名称。Bashbrew ( bashbrew
) 是一个用于克隆、构建、标记和推送 Docker 官方镜像的工具。有关更多信息,请参阅 Bashbrew README
。