谈谈 Docker 的优缺点:一篇 2015 年的文章,与 2026 年的答案

转载与翻译声明

本文译自 Philipp Hauer 的文章 Discussing Docker. Pros and Cons.

  • 原文标题:Discussing Docker. Pros and Cons.
  • 原文作者:Philipp Hauer(Head of Engineering @ commercetools,@philipp_hauer)
  • 原文出处:phauer.com
  • 发布时间:2015-10-25;最后修订:2019-01-20
  • 翻译说明:原文版权归 Philipp Hauer 所有。正文部分为完整中文翻译,文中配图在原文里,此处以文字说明位置;仅供学习交流,如原作者有异议将立即删除。

原文正文(第一节)之后都是本文新增的部分,不代表原作者观点:第二节逐条对照它当年的担忧在今天是否成立,第三节讲这十年底层技术栈被换掉了什么,第四节是一份 2026 年的现代容器技术栈实操清单(多阶段构建、distroless、BuildKit、供应链签名与准入控制……),第五节讲 Docker 在 2026 年的新战场——给 AI Agent 当边界。

十年过去,原文的结论大多已经过时,但它论证的结构一条都没过时,这恰恰是它值得重读的原因。

目录


一、原文正文

Docker 让我们能轻松地为应用创建可复现的环境。我们把环境搭建自动化,消灭了手工易错的操作。这样我们降低了部署过程的风险、提升了它的可靠性。但也有一些挑战,以及一些使用 Docker 会很困难的领域。这篇文章讨论 Docker 的若干优点,也指出一些缺点。

1.1 优点

对开发团队的好处

完全掌控应用的执行环境。 这包括必要的基础设施:JRE、应用服务器、VM 参数,以及应用运行所需的其他环境变量。基础设施可以由开发团队自己轻松、独立地修改,因为他们不必等运维团队去改环境。

(原文配图:一个 Docker 镜像包含应用运行所需的一切,数据库等外部资源除外。)

降低风险。 测试跑的就是最终会在生产服务器上运行的那个镜像。这提升了测试的可靠性。而且发布变得没那么吓人,因为我们在生产上跑的,和在测试、以及构建流水线其他阶段(预发系统、验收测试、容量测试等等)跑的是同一个镜像。

(原文配图:镜像一旦构建完成,就会被用在(持续)交付流水线的所有阶段。这提升了交付的可靠性。)

对运维团队的好处

维护应用环境的开销更小。 环境的创建是自动化的,因此需要的手工操作更少。这降低了风险、提升了可靠性。

在一堆服务器上手工维持一致的环境既容易出错、也可能是噩梦。 用 Docker 创建多个环境实例很容易,因为只要在服务器上执行镜像就行。这样给集群加节点、做水平扩展都很轻松。

更新已有应用和环境很容易。 用传统方式搭建环境(比如安装脚本)时,一旦环境已经存在就会出问题。在脚本里考虑更新路径可能是一项非常复杂的任务(比如检查文件是否存在、清理不再使用的文件)。用 Docker,我们就不必考虑已有环境(数据库除外)。我们只要停掉正在运行的容器、启动更新后的新容器。因为我们总是从空环境开始,搭建过程因此被简化了。

基础设施即代码

用 Docker(以及 Chef、Puppet 这类工具)搭建环境时,我们能享受「基础设施即代码」的好处。基础设施不再是手工创建的,而是一个自动化过程的产物。我们显式地描述自己的基础设施。就 Docker 而言,Dockerfile 就是描述我们环境的那份文档。这带来如下好处:

  • 我们可以把 Dockerfile 纳入版本控制、追踪环境的变化。这样我们始终知道哪个应用版本对应哪个环境版本。
  • 我们得到一份可靠的基础设施文档。我们始终知道应用运行需要什么、环境里当前装了什么。
  • 而且这份文档永远是最新的,因为要改环境就必须改它。
  • 因此,搭建新环境很容易,因为我们知道自己需要装什么。

与其他自动化工具的比较

用 Chef 或 Puppet 这类工具,你也能获得 Docker 的很多优点(可复现的环境、基础设施文档、新节点搭建容易)。但当我们使用(Docker 这样的)虚拟化方案时,还能额外得到:

  • 当我们要实施持续交付时,需要为交付流水线的每个阶段(提交阶段、验收测试、容量测试、预发、生产)搭建多个环境实例。为此使用物理机是不现实的。
  • 应用及其所需环境的安装变得容易,因为我们总是从空 VM 开始(实际上 Docker 并不启动真正的 VM,见下)。旧的 VM 可以完全删掉,重新搭一个新的。

轻量级虚拟化

正因为上面这些原因,Chef 或 Puppet 常常和虚拟化配合使用:先启动一台虚拟机,再用 Chef 或 Puppet 在 VM 里搭建环境。而用 Docker 替代真正的 VM,我们能得到:

  • 轻量级虚拟化。 启动一个 Docker 容器比启动 VM 快得多,因为不需要引导客户机操作系统。这减少了开销。多个容器共享宿主操作系统的内核,但拥有各自的文件系统、用户、网络和进程。从宿主操作系统的视角看,运行一个 Docker 容器不过是启动了另一个进程。这在显著加快容器启动速度的同时,依然提供了不错的隔离性。

1.2 缺点与挑战

在(过分地?)赞美了 Docker 之后,让我们来考虑使用 Docker 时的缺点和挑战:

  • 多了一层,导致复杂度上升。 这不仅影响部署,也影响开发和构建。
  • 此外,管理大量容器本身就是挑战,尤其是要把容器集群化的时候。像 Google Kubernetes 和 Apache Mesos 这样的工具可以帮上忙。
  • 容器共享同一个内核,因此隔离性不如真正的 VM。内核里的一个 bug 会影响每一个容器。
  • Docker 基于 Linux Containers(LXC),这是一项 Linux 技术。因此我们无法在其他系统上运行 Docker,容器也永远是 Linux 系统。不过 Boot2Docker 借助 VirtualBox 让 Docker 能在 Windows 和 Mac OS X 上使用:Docker 客户端跑在宿主 OS 上,通过 VirtualBox 里的 Docker daemon 通信。遗憾的是,这没那么舒服,日常使用比起原生运行 Docker 更笨重、更麻烦。
  • 引入 Docker 可能是一项费力且耗时的任务。 我们必须评估它是否值得这份投入和随之而来的额外复杂度。在我过去的一个项目里,管理层拒绝 Docker 的理由是:生产、预发、实验一共 3 个节点,不足以证明这份投入是合理的……然而,当你拥有一个节点集群(做水平扩展时)、或者使用持续交付时,你就需要一种复现环境的手段,而 Docker 在这件事上非常出色。此外,持续交付能缩短上市时间(因为部署和环境搭建都自动化了,新功能能更快到达生产环境),这可以用来说服管理层。
  • 我还体会到,在监管严格的领域(比如银行业),人们对 Docker 也有顾虑:
    • 运行容器需要 root 权限。 对某些公司来说这是问题——在那些公司里,负责在生产服务器上运行和更新应用的人,不能拥有 root 权限。
    • Docker 的使用是否符合某些必须满足的安全标准(比如 PCI),并不明确。
    • 有些公司有一条可疑的规定:机器上只能安装来自官方/可信来源的软件。举例来说,Red Hat Enterprise Linux 6 里不包含 Docker,因此必须从 docker.com 安装,而那是「不可信来源」。

原文的相关阅读:用 Maven 构建 Docker 镜像


二、2026 现状:逐条对照

原文修订于 2019-01,那时 Docker Engine 还是 18.x;今天是 2026-09,最新是 Docker Engine 29.8.1。逐条看它当年担心的事:

2015 年的担忧2026 年的实况
Docker 是 Linux 技术,非 Linux 只能靠笨重的 Boot2Docker基本解决。Mac 走 Virtualization.framework、Windows 走 WSL2,Docker Desktop 开箱即用;还有 Podman Desktop、Colima、Rancher Desktop、Lima 一堆替代品
跑容器需要 root,生产上不给部分解决:Rootless Docker(20.10 GA)和 userns-remap 都可用。但默认仍是 rootful,rootless 在网络和端口上有额外限制,多数生产环境还是以 root 运行
共享内核,隔离性不如真 VM物理事实没变,但周边补强了:默认 seccomp/AppArmor、capabilities 收敛;要强隔离可上 gVisor / Kata Containers / Firecracker。更戏剧性的是 Docker 自己在 2026 年给出的答案,见第四节
是否符合 PCI 等安全标准存疑议题已经翻篇。OCI 标准化 + NIST SP 800-190《应用容器安全指南》(2017)+ 云厂商全面采用。今天的合规问题不是「能不能用容器」,而是「镜像从哪来、有没有签名和 SBOM」
基于 LXC早就换了:LXC → libcontainer → runc。OCI 在 2015 年成立,定下了镜像规范和运行时规范
集群难管,需要 K8s 或 MesosK8s 赢了(最新 1.37.1);Mesos 已于 2021 年退役进入 Apache Attic

三、2026 现状:骨架被换掉了

这是最大的认知变化:今天说「Docker」,指的通常已经不是那个运行时了。

  • containerd 被拆出来并捐给 CNCF(2017,2019 年毕业),成为今天绝大多数生产集群的实际底层。
  • Kubernetes 1.20 弃用 dockershim,1.24(2022)正式移除。 也就是说,今天跑在 K8s 上的容器,多半根本没用 Docker 的运行时,用的是 containerd 或 CRI-O。
  • Moby 项目拆分(2017):Docker 被拆成开源上游(Moby)和商业产品两条线。
  • Docker Enterprise 于 2019 年卖给了 Mirantis。
  • Swarm 在 1.12(2016)的 Swarm mode 之后就基本休眠了。
  • 构建方式变化最大:多阶段构建(17.05,2017)——这是原文完全没提、但极大改变了镜像瘦身实践的一件事;BuildKit 在 23.0(2023)成为默认,带来并行构建、cache mount、--secret、SSH 转发、docker init;buildx 支持多架构;Compose 进入 V2 和 Compose Spec。
  • 商业模式变了:Docker Desktop 从 2022 年 1 月起对大公司收费(>250 人或 >1000 万美元营收),直接催生了 Podman / Colima / Rancher Desktop 这一波替代;Docker Hub 也开始拉取限流。
  • 供应链安全成为主线:Sigstore/cosign 签名、Notation、SBOM、provenance attestation、Docker Scout。这是原文「只准装可信来源软件」那条担忧的转世——只是现在担心的是镜像里到底有什么。(2024 年初 runc 的 CVE-2024-21626「Leaky Vessels」也提醒了大家:容器运行时本身也是攻击面。)

一句话:今天说 Docker,通常指的是镜像格式 + Dockerfile 语法 + docker CLI 的手感。运行时是 runc/containerd,编排是 K8s,仓库可能是 Harbor/GHCR/ECR。Docker 公司 ≠ Docker 技术。

四、今天实际怎么做:2026 年的容器技术栈

前面两节是「变了什么」。这一节回答一个更实际的问题:如果今天从零开始搭一套容器化的交付链路,具体会用到什么。 这是原文完全没有覆盖的部分——它写于 2015 年,那时多阶段构建还不存在。

4.1 写 Dockerfile:多阶段构建是标配

原文通篇没提镜像怎么构建,因为当年只有单阶段。多阶段构建(17.05,2017)是这十年对日常实践影响最大的一个变化:

# ---- 构建阶段:工具链只活在这里 ----
FROM golang:1.27 AS build
WORKDIR /src

COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download

COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 go build -trimpath -o /out/app ./cmd/app

# ---- 运行阶段:没有 shell、没有包管理器 ----
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

要点:

  • 多阶段:编译器和依赖不进最终镜像。同一个应用的镜像可以从约 1GB 降到 10MB 量级。
  • 基础镜像的选择谱系:alpine(小,但 musl 和 glibc 的兼容坑不少)→ distroless(Google 维护,没有 shell 和包管理器,攻击面极小)→ scratch(空镜像,连 CA 证书都要自己拷)。
  • BuildKit(23.0 起默认):--mount=type=cache 缓存 Go 模块和编译产物;--mount=type=secret 传密钥且不留在镜像层里(这是当年最容易出事的地方);--ssh 转发 agent 做私有仓库拉取。
  • .dockerignore 常常是最大的构建加速杠杆——把 .git、node_modules、测试数据挡在外面。
  • 多架构:docker buildx build --platform linux/amd64,linux/arm64(Apple Silicon 之后这变成了刚需)。
  • docker init 会给你的项目自动生成 Dockerfile、compose 文件和 .dockerignore,作为起点够用。

4.2 运行时与隔离:别再默认 rootful

今天的运行时栈是分层的:docker CLI → containerd → runc(OCI 运行时)。每一层都是可替换的。

默认加固,这几条应该是起点而不是进阶:

  • 非 root 用户:USER nonroot,配合 rootless Docker 或 userns-remap(把容器内的 root 映射成宿主机上的非特权用户)。
  • capabilities 收敛:--cap-drop=ALL,再按需 --cap-add。
  • 只读根文件系统:--read-only,需要写的地方挂 tmpfs 或卷。
  • seccomp / AppArmor / SELinux:Docker 现在有默认 profile,但很多生产环境用的是自己收紧的版本。

要强隔离时的选项:

方案做法代价
gVisor用户态内核,拦截并重新实现系统调用兼容性好但性能有损,syscall 密集的场景明显
Kata Containers每个 Pod 一个轻量 VM,OCI 兼容启动更慢、内存开销更大
FirecrackerAWS 的 microVM,Lambda 底层需要自己管 VM 生命周期

这一节和原文第 3 条缺点(共享内核)的关系:十年前的标准回答是「隔离性够用了,性能更重要」。今天多了一个更诚实的版本——隔离强度是威胁模型的函数。面对你写的、行为确定的程序,namespace 够用;面对会主动探测边界的行动者,就不够。Docker 自己在 2026 年给出的答案就是 microVM,见下一节。

4.3 编排:K8s 赢了,但别急着上

Kubernetes 已是事实标准(最新 1.37),但今天的共识和 2015 年一样值得重复:「3 个节点值得上 K8s 吗」和当年「3 个节点值得上 Docker 吗」是同一个问题。

  • 小规模:单机 Docker Compose 往往就够了。再往外一点有 K3s(轻量 K8s 发行版)、Nomad。
  • 标准做法:Helm / Kustomize 管清单,ArgoCD / Flux 做 GitOps(集群状态由 git 仓库声明并自动收敛)。GitOps 是这些年最大的实践变化——它把原文「基础设施即代码」那条推到了部署阶段。
  • 网络与策略:Cilium(基于 eBPF 的数据面)成为主流,Gateway API 正在取代 Ingress。
  • Service mesh 在降温:不是不需要,而是很多能力下沉到了 eBPF 和 K8s 本身,Istio 的 sidecar 模式正在被 sidecarless(Ambient 模式)替代。

4.4 供应链安全:这是 2026 年新增的最重要一层

原文那条「有些公司规定只能装官方/可信来源的软件,而 docker.com 是『不可信来源』」——这个问题今天有了完整的工程答案,而且形式完全变了。

今天的问题不是「从哪下载的」,而是「这个镜像的每一层是谁构建的、里面有什么、有没有被改过」。对应的工具链:

环节工具
生成 SBOM(软件物料清单)Syft(SPDX / CycloneDX 格式)
漏洞扫描Trivy、Grype、Docker Scout
签名cosign(Sigstore,支持 keyless 签名)、Notation(CNCF Notary Project)
来源证明SLSA provenance、in-toto attestation
准入控制Kyverno / OPA Gatekeeper——在集群入口强制「镜像必须已签名且扫描通过」才允许调度

2024 年初 runc 的 CVE-2024-21626(「Leaky Vessels」) 是个提醒:容器运行时本身也是攻击面。这条链路上没有任何一环是「装完就不用管」的。

4.5 开发体验

  • Dev Containers:把开发环境也写进版本控制(devcontainer.json)。这才是原文「基础设施即代码」最彻底的形态——连你同事的编辑器配置都开始可复现了。
  • 本地运行时:Docker Desktop、Mac 上的 OrbStack、Podman Desktop、Colima、Rancher Desktop。选择比当年多了太多。
  • Compose Watch:改代码自动同步进容器并重启,不再每次 rebuild。
  • 远程/混合开发:Tilt、Skaffold、Telepresence、Garden 把「本地开发 + 集群调试」接起来;现在还有云端开发沙箱。

4.6 还没成气候,但值得盯

  • Wasm:Spin / SpinKube、wasmCloud。冷启动毫秒级,沙箱模型比容器更严格(默认没有文件系统、没有网络,能力全靠显式授予)。
  • microVM 的复兴:Firecracker、Cloud Hypervisor。这条线在 2026 年被 Docker 自己推了一把,原因见下一节。
  • 容器和虚拟机那条线在变模糊:容器做隔离,microVM 拿性能,两者的边界正在往中间靠。

4.7 如果今天从零开始

把上面收成一份清单:

  1. Dockerfile 用多阶段 + BuildKit + distroless,.dockerignore 写全
  2. 本地 用 Docker Desktop / OrbStack,团队环境用 Dev Containers 固化
  3. 镜像 用 cosign 签名、Syft 出 SBOM、Trivy 扫漏洞,全部进 CI
  4. 集群 3 个节点以内先别上 K8s;要上的话 Helm + ArgoCD 起步
  5. 运行时 非 root + cap-drop + 只读根文件系统,作为默认而非例外
  6. 准入 用 Kyverno 强制「未签名不允许部署」

五、2026 现状:Docker 的新战场是 AI Agent

这一节是原文完全无法预见的,但读起来会让人起鸡皮疙瘩——因为它恰好是原文第 3 条缺点的正面回应。

2026 年 9 月,Docker 总裁 Mark Cavage 在 WeAreDevelopers 的主题演讲里,把公司的重心明确压在了 AI coding agent 的沙箱上。Docker 官方博客的原话是:

容器打包应用。沙箱容纳 agent。

容器打包应用,它共享宿主内核,用 namespace 和 cgroup 给一个固定的工作负载提供自己的文件系统、网络和进程视图。对「跑起来、干完活、只碰交给它的东西」的软件来说,这是对的工具。

然而 agent 是一个概率性的行动者。它自己决定下一步做什么,然后去做——对着你的文件系统、网络、凭证和云账号。它会装一个需要 root 的包,会开一个没人规划过的端口,被挡住就换个方法。容器不是为此设计的:边界就是那个行动者正在探测的同一个内核。

于是 Docker 的答案是 microVM:

  • Docker Sandboxes:每个 agent 拿到自己独立的 microVM 和自己的内核,与开发者宿主环境隔离。开发者可以选择 agent、模型和工具,然后定义这个沙箱对文件、网络和密钥的访问权限——而这些策略在 agent 的控制范围之外。Docker 的说法很直白:正因为边界在下面,所以可以放心给 agent root 权限,让它放开了跑。
  • Docker Sandbox Kits:把 agent、它的工具、以及「它能碰什么」的规则,打包成一个带版本、可分享的产物。2026 年 9 月 24 日发布的 Kit 规范 v3 里,Kit 已经就是一个普通的 OCI 镜像了(没有自定义 media type、没有 sidecar 文件),清单里用一条 annotation vnd.docker.sandbox.kit.descriptor 承载声明。规范以 Apache 2.0 开源在 docker/sandbox-kit-spec,并且 Docker 已承诺将其提交给 CNCF 做中立治理。
  • Docker Cloud Sandboxes:同样的 microVM 隔离,从笔记本延伸到 Docker 托管的云算力,按量付费,sbx 工作流一条命令把活儿搬上云。
  • 免费的独立 CLI,可用。

Docker 给这件事提炼的口号,值得单独抄下来:

Dockerfile 让软件可复现,Kit 让权限可复现。 (Dockerfiles made software reproducible. Kits make authority reproducible.)

这两件事其实是同一个论证的闭环。 原文十一年前说:Dockerfile 就是环境的文档,而且它永远是最新的,因为不改它就改不了环境。今天这句话被原样搬到了权限上——当年的问题是「我知道环境里装了什么吗」,今天的问题是「我知道 agent 能碰什么吗」。同一个论证结构,换了个宾语。

还有一个反转挺有意思。原文第 3 条缺点说「共享内核,隔离性不如真 VM」,十年前 Docker 阵营的标准回答是「隔离性够用了,性能更重要」。2026 年 Docker 自己给出的答案是:用 microVM。

这不是 Docker 认错了,而是威胁模型变了:面对一个你自己写的、行为确定的程序,namespace 够用;面对一个会主动探测边界、被挡住就换个方法的行动者,就不够了。隔离强度从来不是绝对的技术指标,它是威胁模型的函数。

六、哪些没变

1. 复杂度那条,反而变本加厉。 原文说「多了一层」。今天是一层套一层:容器 → 编排 → service mesh → GitOps → 内部开发者平台。当年「3 个节点值得上 Docker 吗」的辩论,今天原样搬到了「3 个节点值得上 K8s 吗」——同一个争论,只是往上挪了一级。

2. 组织与合规的摩擦还在,只是议题换了。 从「能不能用容器」换成「镜像治理、仓库策略、许可证成本」。原文用整整一段讲「怎么说服管理层」,那一节今天依然有实际价值。

3. 原文最有价值的两段——管理层的「不值得」论、强监管领域的阻力——恰恰是唯一没过时的部分。 技术结论全都过时了,论证结构一条都没过时。

七、可以带走的东西

1. 判断一个技术观点是否过时,要看它的约束条件变没变,而不是看它的结论。 这篇文章的结论类论断(「Go 没有泛型」式的那种)几乎全过时了,但它的论证方式——「3 个节点够不够支撑这份复杂度」「持续交付缩短上市时间,所以值」——今天换个技术栈照样成立。

2. 技术选型的说服永远是算经济账,不是比谁先进。 原文说服管理层的抓手不是「Docker 很酷」,而是「持续交付缩短 time-to-market」。这条在 2026 年做 AI Agent 沙箱、做平台工程、上 K8s 时,一样适用。

3.「永远从空环境开始」是一个可迁移的洞察。 原文用它论证更新为什么简单;十年后同样的论证被搬到了权限上(Kit 让权限可复现)。当你想让某样东西可审计、可 diff、可回滚时,先想办法让它从零重建,而不是打补丁。

4. 隔离强度是威胁模型的函数,不是技术排名。 同一个技术,面对确定的程序和面对概率性的行动者,需要的边界强度完全不同。这条在 2026 年做 agent 系统时比在 2015 年做微服务时更值钱。


附:原文与参考资料

原文

本文新增部分的依据(截至 2026-09)