VPS上的Docker生产环境:会出什么问题以及如何解决

3 分钟阅读·Matthieu·productionsecurityvpsdocker-composedocker|

Docker在你的笔记本上运行正常。在公网VPS上,它会绕过防火墙、用日志填满磁盘、以root权限运行所有进程,且没有更新策略。以下是你需要解决的8个问题。

你在VPS上安装了Docker,运行了docker compose up,应用上线了。搞定了?

没那么简单。在你的笔记本上,没人扫描你的端口,磁盘空间充足,安全性无关紧要。在面向互联网的VPS上,Docker的默认配置会主动跟你作对。

本页涵盖你将遇到的8个问题,并链接到每个问题的专门指南。浏览一遍,找到适用于你服务器的内容,跟着详细指南操作。

前置条件

本指南假设你已经了解Docker基础知识。如果需要补课:

  • Docker命令和概念
  • 使用Compose运行多服务应用

本文中的所有命令和输出均在Debian 12和Ubuntu 24.04上使用Docker Engine 29.x和Compose v2(版本5.x)测试通过。

在公网VPS上运行Docker会出什么问题?

将Docker从开发环境迁移到公网VPS时,有八个问题会出现:防火墙失效、容器以root权限运行并拥有完整的主机访问权限、日志在没有轮转的情况下填满磁盘、容器网络与主机网络冲突、服务没有资源限制和健康检查、卷没有备份策略、端口在没有TLS的情况下暴露、容器镜像在没有更新计划的情况下过期。这些都是Docker的默认行为,每一个都会在生产环境中给你造成麻烦。

详细指南前的快速概览:

# 问题 诊断命令 风险等级
1 防火墙被绕过 sudo iptables -L DOCKER-USER -n 严重
2 Root容器 docker info --format '{{.SecurityOptions}}' 严重
3 日志填满磁盘 du -sh /var/lib/docker/containers/*/*-json.log
4 网络冲突 docker network ls
5 没有资源限制 docker stats --no-stream
6 没有卷备份 docker volume ls
7 没有反向代理/TLS ss -tlnp | grep -E ':80|:443' 严重
8 镜像过期 docker compose images

现在就在你的服务器上运行这些命令。如果有任何输出让你意外,请阅读下面对应的章节。

Docker会绕过你的防火墙吗?

会。Docker直接操作iptables,在natfilter表中插入规则,这些规则在UFW或firewalld看到流量之前就已被评估。当你用-p 8080:80发布端口时,该端口对整个互联网开放,即使你的UFW规则设置了deny incoming

检查这是否影响到你:

sudo iptables -L DOCKER-USER -n -v

如果输出显示一条空链或只有一条无条件的RETURN规则而没有过滤,那么每个发布的端口都是完全开放的。

根本原因:发往Docker容器的数据包经过FORWARD链和DOCKER链。UFW在INPUT链上运行。两者永远不会交汇。

最简单的即时修复方法是将发布的端口只绑定到127.0.0.1

ports:
  - "127.0.0.1:8080:80"

这使端口只能从主机本身访问。配合反向代理来处理外部流量。

关于DOCKER-USER链修复、UFW集成和nftables配置的完整指南。

在VPS上以root运行Docker危险吗?

默认情况下,Docker守护进程以root运行,容器在其命名空间内也以root运行。如果攻击者逃出容器,就会以root身份登陆主机。在公网VPS上,这直接导致服务器被完全入侵。

检查你当前的配置:

docker info --format '{{.SecurityOptions}}'

在输出中查找name=rootless。如果不存在,你的守护进程以root运行。

同时检查你的容器是否在内部以root运行:

docker ps -q | xargs -I {} docker exec {} id

如果大多数容器显示uid=0(root),它们在容器内以root运行。在Dockerfile中设置了USER appuser的镜像会显示非root的uid。

你有三层防御:

  1. Rootless模式在非root用户下运行整个Docker守护进程。即使容器逃逸也只会以非特权用户身份登陆主机。
  2. Seccomp配置文件限制容器可以使用的系统调用。Docker自带的默认配置文件阻止约44个危险的系统调用,但你可以进一步收紧。
  3. AppArmor / SELinux在所有其他措施之上添加强制访问控制。

Rootless Docker的完整配置、自定义seccomp配置文件和no-new-privileges。

为什么Docker容器会填满你的磁盘?

Docker的默认日志驱动是json-file,没有大小限制也没有轮转。你的应用写到stdout的每一行都存储在/var/lib/docker/containers/<id>/<id>-json.log中。一个繁忙的Web应用可以在几天内生成几个GB的日志。

检查日志目前占用了多少空间:

sudo du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head -5

在一个运行了几周没有日志轮转的VPS上,你可能会看到这样的输出:

4.2G    /var/lib/docker/containers/a1b2c3.../a1b2c3...-json.log
1.8G    /var/lib/docker/containers/d4e5f6.../d4e5f6...-json.log
256M    /var/lib/docker/containers/g7h8i9.../g7h8i9...-json.log

日志不是唯一的磁盘消耗者。检查Docker的整体磁盘使用情况:

docker system df

运行5个服务的服务器的典型输出:

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          12        5         4.2GB     2.8GB (66%)
Containers      8         5         52MB      12MB (23%)
Local Volumes   6         4         1.1GB     245MB (22%)
Build Cache     18        0         890MB     890MB (100%)

构建缓存100%可回收。那7个未使用的镜像占了2.8 GB。在40 GB的VPS磁盘上,这些很快就会累积起来。

解决方案分两部分:在/etc/docker/daemon.json中配置日志轮转,以及定期清理未使用的镜像和构建缓存。

完整的日志轮转配置、磁盘监控和自动清理:

Docker网络在单台VPS上如何工作?

Docker创建自己的桥接网络(默认172.17.0.0/16)并在内部管理容器IP。在VPS上,这可能与你的主机网络、VPN子网或其他服务冲突。

查看Docker创建了哪些网络:

docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
f6e5d4c3b2a1   host      host      local
9i8h7g6f5e4d   none      null      local

每次docker compose up都会创建一个额外的网络。几个项目之后,你可能有十几个子网重叠的网络。

VPS网络的关键决策:

  • 桥接模式(默认):容器获得内部IP,端口通过iptables发布。适用于大多数配置。增加了一层NAT。
  • Host模式:容器直接共享主机的网络栈。没有NAT开销,但没有端口隔离。适用于对性能敏感的服务。
  • 自定义桥接网络:同一自定义网络上的容器可以通过容器名互相访问。这是Compose自动创建的。

在VPS上的要点:在docker-compose.yml中显式定义你的子网,而不是让Docker选择。这可以避免与你的托管商内部网络发生冲突。

桥接模式与host模式对比、自定义子网和容器间DNS的详细指南:

没有资源限制和健康检查会发生什么?

没有内存限制时,一个存在内存泄漏的容器会消耗所有可用RAM,触发Linux OOM killer,并导致无关的容器甚至SSH守护进程崩溃。没有健康检查时,Docker无法知道容器已经出故障了。它保持"running"状态,同时返回错误。

检查你的容器是否设置了限制:

docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}"

如果MEM %列显示了值但你从未设置过限制,这些百分比是相对于主机总RAM计算的。显示45%的容器正在使用你整个VPS内存的45%,而且没有任何东西阻止它占用更多。

docker-compose.yml中的最小生产配置:

services:
  app:
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    restart: unless-stopped

restart: unless-stopped策略自动重启崩溃的容器但尊重手动停止。healthcheck让Docker标记不健康的容器并根据重试次数重启它们。

Compose资源限制、健康检查模式和重启策略的完整指南:

如何备份Docker卷?

Docker卷在容器外持久化数据。如果卷被删除或损坏,你的数据库、上传文件或配置就会丢失。Docker没有内置的卷备份机制。

列出你的卷及其大小:

docker system df -v | grep -A 100 "Local Volumes space usage"

或检查各个卷的挂载点:

docker volume ls -q | xargs -I {} docker volume inspect {} --format '{{.Name}}: {{.Mountpoint}}'

挂载点默认在/var/lib/docker/volumes/下。你可以用标准Linux工具备份它们,但需要先停止或暂停容器以避免不一致的快照。对于数据库,运行中数据库的文件系统副本不是可靠的备份。你需要先做一个dump。

备份策略、恢复流程和自动化调度:

如何用TLS暴露Docker服务?

在每个容器上直接用-p 443:443发布端口无法扩展到一个以上的服务。你需要一个反向代理来终止TLS、将流量路由到正确的容器并处理证书续期。

检查你的VPS上当前监听的内容:

ss -tlnp | grep -E ':80|:443'

如果你看到应用容器直接绑定到80或443端口,它们在没有代理层的情况下暴露。这意味着每个服务都需要自己的证书管理,而且你无法在一个VPS上托管多个域名。

三个反向代理选项主导Docker生态系统:

代理 自动发现 自动TLS 配置方式
Traefik 是(Docker标签) 内置Let's Encrypt 标签 + YAML
Caddy 通过插件 默认自动 Caddyfile
Nginx Proxy Manager Docker socket Let's Encrypt界面 Web界面

Traefik是Docker原生部署最常见的选择,因为它读取容器标签并自动生成路由规则。添加服务时无需更新配置文件。

各选项的对比、安装指南和TLS配置:

VPS上Docker容器的更新策略是什么?

Docker镜像不会自行更新。如果你运行postgres:16而安全补丁在postgres:16.4中发布,你的容器会继续运行旧镜像,直到你显式拉取并重建容器。

检查你的服务正在使用哪些镜像:

docker compose images

将镜像摘要与最新可用版本进行比较:

docker compose pull --dry-run 2>&1

如果dry-run输出中有镜像显示为"pulled",你的运行版本已经过期。

关键决策:

  • 固定镜像标签:在生产环境中永远不要使用:latest。使用特定版本标签如postgres:16.4或SHA摘要。
  • 定时拉取:按计划运行docker compose pull && docker compose up -d,但先在staging环境测试更新。
  • 零停机重启:使用健康检查和docker compose up -d --no-deps <service>逐个更新服务。
  • 镜像扫描:Trivy等工具在部署前扫描你的镜像是否存在已知CVE。

更新策略、镜像固定模式和零停机部署:

生产环境运行Docker需要Kubernetes吗?

不需要。Docker Compose是单服务器工作负载的生产就绪部署工具。Kubernetes解决的是多节点编排、自动扩缩容和跨集群自愈。如果你的应用在一台VPS上运行,Compose提供了你所需的一切,没有Kubernetes的运维开销。

Compose适用的场景:

  • 运行3-15个容器的单台VPS
  • 静态或可预测的流量模式
  • 没有专职平台工程师的小团队
  • 更新期间可以容忍几秒停机的服务

超出Compose能力的场景:

  • 需要多台服务器实现高可用
  • 基于流量峰值的自动扩缩容
  • 包含数百个微服务的复杂服务网格
  • 需要跨节点蓝绿部署的零停机要求

轻量级的中间方案是K3s,一个精简版Kubernetes,可以在单台VPS上运行,大约占用512 MB的RAM开销。但对于大多数个人项目、SaaS MVP和小型生产应用,Compose是正确的工具。

生产环境的Docker VPS需要多少RAM?

运行Docker生产环境的VPS至少需要4 GB RAM来支持3-5个容器。Docker守护进程本身大约使用100-200 MB。每个容器在此基础上增加自己的内存占用。如果没有设置内存限制,一个行为异常的容器就能耗尽所有资源。

VPS规格 容器数量 适用场景
4 GB RAM 3-5个轻量级 博客、API、数据库、Redis
8 GB RAM 5-10个混合 SaaS MVP、多服务、监控栈
16 GB RAM 10-20个 多项目、CI runner、较重的数据库
32 GB RAM 20+个 AI推理、大型数据库、构建服务器

存储和RAM同样重要。Docker镜像、卷、日志和构建缓存会不断累积。计划至少40 GB的NVMe存储,并从第一天起就配置中描述的监控。

对于容器化Web服务来说,CPU很少是瓶颈。4个vCPU可以处理大多数工作负载。例外情况:视频转码、AI推理和构建服务器。

需要适配Docker生产工作负载的NVMe存储VPS,请查看Virtua Cloud VPS方案

生产环境检查清单

在VPS上将Docker投入生产之前,逐项检查:

  • 防火墙规则阻止所有Docker发布的端口(通过反向代理的除外)
  • 容器尽可能以非root用户运行
  • Docker守护进程以rootless模式运行,或容器使用seccomp + no-new-privileges
  • 日志轮转已在/etc/docker/daemon.json中配置
  • docker system prune按计划运行(每周cron)
  • 每个服务在docker-compose.yml中都有内存和CPU限制
  • 每个服务都有健康检查
  • 重启策略已设置(unless-stoppedon-failure
  • 包含重要数据的卷有自动备份
  • 反向代理处理TLS终止和证书续期
  • 镜像标签固定到特定版本,不使用:latest
  • 你有经过测试的更新流程来拉取新镜像
  • journalctl -u dockerdocker logs <container>可以工作,你知道在哪里查看

如果你无法勾选每一项,请阅读上面各章节中链接的文章。


版权所有 2026 Virtua.Cloud。保留所有权利。 本内容为 Virtua.Cloud 团队原创作品。 未经书面许可,禁止复制、转载或再分发。

准备好亲自尝试了吗?

在可靠的VPS上运行Docker生产环境。

查看 VPS 方案
VPS上Docker生产环境:8个问题及解决方案