Docker 可以在生产环境中运行数月而不出现明显问题。容器启动,应用响应,一切正常。但一个暴露的端口或一个配置错误的权限就能为攻击者打开一扇门。大多数 Docker 安全问题看起来没什么大不了,直到出了问题才会显露原形。
本文介绍了容器环境中的具体风险配置,解释了每种配置如何被攻击者利用,最后提供了一份检查清单,你可以立即对自己的设置进行检查。
为什么 Docker 的安全性比看起来更复杂
容器具有隔离的感觉。你启动一个容器,它运行自己的进程空间,从容器内部看,其他容器不存在。你确实获得了隔离,但只是部分隔离。容器共享主机的内核,这意味着在特定条件下,容器内的进程可以完全访问主机系统。
Docker 服务器开箱即用,针对开发者便利性调优,不适合直接用于生产环境。Root 权限默认启用。所有端口可绑定到任意网络接口。没有运行时监控。大多数开发者接受这些设置,部署容器后就继续开发。这种做法足以快速上手,但不是完整的安全配置。
According to Red Hat 2024年 Kubernetes 安全报告,67% 的组织因容器或 Kubernetes 安全问题而延迟或放缓应用部署。这种延缓通常不是源于攻击,而是团队发现他们的容器配置需要加固,但之前没有做到。
我们经常看到容器在生产环境中运行的配置和开发者本地机器上的配置完全一样。这正是 Docker 安全问题容易悄悄累积的地方,直到某次审计或故障发生,才会浮出水面。
导致这些差距的错误是具体的、可预测的,而且大多数可以避免,从配置层开始就能防止。
常见 Docker 配置错误
容器安全漏洞很少源于零日漏洞。大多数情况下,问题出在初始配置上——当时没有充分考虑网络暴露面和权限范围。
Docker 的默认配置开箱即用。功能完善和真正安全之间存在差距,这正是 Docker 容器安全风险积累的地方,尤其在那些部署后就不再维护的自托管环境中。
我们经常看到这种情况:容器运行在公网 IP 服务器上,端口绑定、用户设置和网络配置都保持着初始部署时的样子。
以 Root 身份运行容器
启动 Docker 容器时如果不指定用户,容器会以 root 身份运行。这意味着容器内的任何进程(包括你的应用程序)都拥有该容器命名空间内的 root 级权限。

容器内的 root 权限与宿主机上的 root 不同,但两者的隔离并不绝对。针对容器运行时的提权漏洞(比如著名的 runc CVE-2019-5736 和类似的运行时缺陷)通常需要容器内有 root 进程才能成功利用。
非 root 容器消除了这类漏洞利用所依赖的 root 进程需求,大幅缩小了这类漏洞的攻击面。不过,它们并不能完全消除容器逃逸风险。
在 Docker 文件中添加 USER 指令可以解决这个问题。一些官方镜像内置了非特权用户,你可以通过 USER 指令激活它们,但很多镜像仍然默认以 root 运行,没有预配置的应用用户。在这种情况下,你需要在 Docker 文件中创建用户,然后切换到该用户。对于大多数自托管部署,这一个改动就能消除整个类别的权限提升风险。
向公网暴露过多端口
当您使用 Docker 发布端口时,Docker 会直接写入自己的 iptables 规则。这些规则在主机级防火墙规则之前执行。这是一个 社区报告的已知行为 and 在 Docker 的数据包过滤指南中有记录,而不是配置错误,这意味着 UFW 等工具无法阻止 Docker 已经打开的连接。

Docker 直接写入 iptables,绕过许多 Linux 主机上的 UFW 和 firewalld 默认规则。这意味着绑定到 0.0.0.0 的端口即使在防火墙看起来已配置的情况下,也可能被公开访问。云安全组和 DOCKER-USER 链规则仍然可以阻止该流量,所以实际的暴露范围取决于你的具体网络设置。
尽可能将服务绑定到 127.0.0.1,通过反向代理路由公网流量,只发布真正需要外部访问的服务。反向代理是控制主机对外暴露内容最可靠的方式。
忽视容器之间的网络隔离
该网络上的任何容器都可以无限制地访问网络上的其他容器。默认网桥在共享它的容器之间不应用任何流量过滤,大多数设置从不改变这个配置。

如果一个容器被攻破,这种开放通信就会成为横向移动的路径。前端容器可以访问数据库、内部 API 或同一默认网桥网络上的任何其他服务,即使那些访问从未被意图允许。
用户定义的网络让你能显式控制哪些容器可以通信,但所有服务共享一个自定义网络仍然允许容器间的自由通信。真正的隔离需要将不应该互相通信的服务放在独立的网络上。关闭默认网桥只是起点,不是终点。
忽视 Docker Socket
位于 /var/run/docker.sock 的 Docker socket 是整个 Docker 引擎的控制接口。将它挂载到容器中会给该容器直接 API 访问主机上运行的守护进程的权限。

有了这种访问权限,容器可以启动新容器、挂载主机目录、检查和修改运行中的容器,并有效地控制主机。攻击面等同于主机上的 root,这就是为什么任何需要 socket 访问的工具都值得仔细评估。
对于大多数用例,存在更安全的替代方案:作用域受限的 API 或 Docker 管理工具 不需要 socket 访问。Docker-in-Docker 有其自身的安全性和运维权衡,不是直接的替代品。
配置错误造成初始暴露。镜像和依赖选择决定了暴露如何随时间复合增加。
随容器存活的镜像和密钥错误
停止容器时,其中的配置错误也随之停止。当你从包含漏洞或硬编码凭证的镜像重新构建时,问题会随容器重新启动。镜像级的错误不会在部署之间重置。
它们随镜像传播到拉取它的每个环境、存储它的每个仓库,以及运行它的每个团队成员。这种持久性使镜像和密钥管理成为一类独特的风险,值得与配置风险分开审计。
我们经常看到这种情况:一个在项目启动时精心选择的镜像从未重新构建过,逐渐偏离它最初代表的安全基线。
使用不受信任或过时的镜像
公共仓库对任何人开放。恶意镜像曾通过 Docker Hub 分发,其中包含加密矿机和嵌入在层历史中、在容器重启之间持续存在的后门。在拉取镜像前进行验证很重要,特别是来自非官方或未知发布者的镜像。

另一个问题是陈旧性。你六个月前拉取的官方镜像从未重新构建过,随着针对其软件包披露的每个 CVE,它一直在累积未打补丁的 Docker 漏洞。镜像没有破损。它只是不再是最新的。
Sonatype 的 2024 年软件供应链现状报告 发现 95% 的情况下消费漏洞组件时,修复版本已经可用,80% 的应用依赖保持超过一年未升级。这个模式同样适用于 Docker 基础镜像,因为它们依赖相同的开源软件包。
Use official images from verified publishers and pin specific version tags rather than relying on “latest.” Build a regular rebuild cadence to keep your images current.
在 Docker 文件和 Compose 文件中硬编码密钥
写入 Docker 文件 ENV 或 ARG 指令、硬编码在 Compose 环境块中、作为构建参数传递或存储在已提交到版本控制的 .env 文件中的凭证,在停止容器时不会消失。它们保留在镜像层历史或源代码控制中,任何能访问这些地方的人都可以读取。

这是最容易被忽视的安全问题之一,因为在开发阶段不会出现明显的问题。API 密钥在 ENV 指令中工作正常。但它也会被纳入代码库、烤入镜像,并随着镜像的分发而传播到任何地方。
Modern Docker Compose 原生支持密钥管理,在运行时挂载凭证,无需将其硬编码到镜像中。Docker 的密钥 API 和外部密钥管理器遵循同样的原则。这些选项确保凭证完全不会进入构建产物和已提交的文件。
运行时环境变量比硬编码凭证更安全,但仍然会在 Docker inspect 输出、日志和崩溃转储中暴露。这是对内置密钥的改进,但不是完整的解决方案。
不定期更新容器镜像
长期运行同一个镜像是常见做法。每当新漏洞公开后,在你重建镜像之前,容器的安全风险窗口就在扩大,但表面上看不出任何变化。
制定一个稳定的重新构建计划。尽可能实现自动化,定期对当前镜像运行漏洞扫描。目标不是完美无缺。而是缩短补丁发布和部署之间的时间间隔。
快速部署时,访问控制和监控往往被忽视。但这两个环节也最容易让问题长期无人发现。
访问控制和可见性缺陷
容器运行在稳定的配置和最新镜像之下,还会面临两类失败。两者都是隐形的:访问控制漏洞在被利用之前你看不出来,监控盲点在需要调查未记录的活动时才会暴露。
The same Red Hat 2024 research 调查发现,42% 的团队缺乏足够的能力来应对容器安全和相关威胁。
我们发现,监控盲点通常在事故调查时才会暴露出来,而不是提前发现。等到可观测性成为优先事项时,往往已经是在应对问题,而不是预防问题。
身份验证不足和管理面板暴露
公网上没有身份验证的容器管理面板,攻击者不需要多高明的技术。只需知道地址就够了。这个门槛比大多数团队意识到的要低得多。

自托管监控和管理工具通常带有可在所有网络接口上访问的网页界面。在公网IP上暴露这些界面而不配置身份验证,就像是把管理后台的门敞开着,风险等同。
身份验证、反向代理和私有网络部署是基础设施。访问控制是你在任何管理界面上添加的配置步骤,而不是默认启用的功能。
同样的原则适用于 Docker CLI 和 GUI 管理; 无论通过哪个界面,对守护进程的管理员级访问都存在相同的风险。
看不到容器在做什么
如果容器被攻击,攻击者的活动会留下痕迹:进程行为的变化、异常的网络连接、意外的文件修改。但如果没有日志收集机制,这些痕迹就无法被记录成可以采取行动的形式。
集中式日志收集、容器审计日志和运行时监控工具为你提供数据,让你在异常活动扩大之前检测到它。目标不是分析每一行日志,而是在需要调查时能够获取数据。
生产环境中运行的容器如果没有日志管道和告警机制,这不叫低维护,这叫没有监管。这两种运维状态完全不同。
为什么基础设施环境也很关键
容器安全始于配置,但配置是运行在基础设施之上的。一台网络配置不当、资源共享、或没有网络层过滤的主机会为其上的所有容器创造有利的攻击条件。正确配置容器和正确配置服务器是两项独立的任务。
许多 Docker 安全漏洞是由容器自身继承的条件放大的:
- 共享租户服务器,租户之间没有硬件隔离
- 主机内核未打补丁
- 主机没有内置的网络层过滤
这并不能消除上述配置步骤的必要性,因为正确的容器加固无论如何都很重要。在隔离的基础设施上开始部署可以消除一层顾虑。
在 Cloudzy,我们根据你的需求提供两种方案:
- Linux VPS:一个干净的环境,让你自己部署 Docker 并应用本文中的加固步骤
- Portainer VPS:一个一键选项,预装 Portainer;服务器启动后,你已经在仪表盘中
两个选项都运行在相同的基础设施上:KVM 虚拟化、AMD Ryzen 9 CPU 处理器(加速时钟可达 5.7 GHz)、DDR5 内存、NVMe SSD 存储、最高 40 Gbps 网络、通过 BuyVM 过滤提供免费的 DDoS 保护,覆盖全球 12 个地点,99.95% 正常运行时间 SLA。
如果想深入了解在 VPS 上运行 Portainer,我们有专门的文章讨论这个话题。
Docker 部署实用安全清单
上述 Docker 安全错误大多来自一次性配置决策,之后就再也没有重新审视过。用这份清单对现有部署进行检查能发现这些漏洞。它是一份审计工具,不是部署指南。
这些 Docker 安全最佳实践介绍如何保护 Docker 容器免受上述最常见的配置失误。
快速参考:全部 9 个错误
| Mistake | Category | 一行修复 |
| Running as root | Configuration | Add USER 指令加入你的 Docker 文件 |
| Ports bound to 0.0.0.0 | Configuration | 绑定到 127.0.0.1 并通过反向代理路由 |
| No network isolation | Configuration | 根据访问需求,在独立的用户定义网络中分离服务。 |
| Docker socket 已挂载 | Configuration | 移除该挂载;使用作用域限制的 API 或替代方案 |
| 镜像来源不可信或已过时 | Image | 使用官方镜像并指定版本标签 |
| Hardcoded secrets | Image | 将凭证移至运行时环境变量或密钥管理器 |
| 没有镜像重建计划 | Image | 设定每月重建周期,尽可能自动化 |
| Unauthenticated dashboards | Access | 添加身份验证并将管理界面移至私有网络 |
| 未收集容器日志 | Access | 建立集中式日志记录和运行时监控 |
我们建议先在现有设置上运行,因为问题最可能已经存在于那里。
容器以非 root 用户运行: 检查你的 Docker 文件中是否有 USER 指令。如果不存在,容器将以 root 身份运行。
端口绑定限制在 localhost 或通过代理: 运行 docker ps 并检查端口绑定。0.0.0.0:PORT 条目在没有上游安全组、外部防火墙或 DOCKER-USER 链规则阻止的主机上可能被公开访问。
使用自定义桥接网络: Docker 默认桥接上的容器可以自由相互通信。同一用户定义桥接上的容器仍能相互通信,因此应按信任边界将服务拆分到不同网络以实现真正的隔离。
Docker socket 未挂载到容器中: 检查 Compose 文件和运行参数。如果 /var/run/docker.sock 作为卷出现,确认这是必需且有意为之的。
基础镜像来自已验证发布者并指定版本: FROM ubuntu:latest 会拉取未指定版本的镜像,可能已过时。应指定具体的发布版本。
Docker 文件、Compose 文件或构建参数中没有密钥: 镜像层历史会在容器删除后保留凭证。使用 Compose 密钥、Swarm 密钥、构建密钥挂载或外部密钥管理器。运行时环境变量比硬编码值更安全,但仍会出现在检查输出和日志中。
定义了镜像重建计划: 旧镜像会积累漏洞。每月重建周期使大多数设置的风险窗口保持在可管理水平。
管理界面受身份验证保护: 任何没有身份验证的公网 IP 上的仪表板都是开放的入口点。尽可能将其放在私有网络中是更好的做法。
正在收集容器日志: 没有日志管道,事件检测依赖于可见的系统影响。这是行动的滞后信号。
Conclusion
Docker 的默认配置为了便利而设计,不是为了安全。本文涉及的大多数错误源于初始部署后从未改动的设置,而非复杂的攻击。
大多数修复只需一次性配置决策:一条 USER 指令、一次端口绑定改动、一个自定义网络、一个重建计划。对于大多数设置,这些都不需要新的工具。
首先要解决容器配置。其次是它运行的基础设施。两者都重要,互不替代。