SSD Nodes Learn 🎉 VPS $4.99/月起
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-07

VPS 上运行 Docker 到底有哪些变化?

VPS 上的 Docker 引擎不变,但内存不足会触发 OOM,发布端口可能绕过 UFW,容器重启后不会自动恢复,镜像和缓存还会填满磁盘。

在 VPS 上运行 Docker 时会有哪些变化

VPS 上的 Docker 使用与笔记本电脑上相同的引擎和镜像,因此您已经熟悉的命令仍然可以使用。变化在于运行环境。笔记本电脑通常有可用内存,防火墙也很少有人扫描,磁盘容量足够大,您几乎不需要关注磁盘使用情况。租用的服务器则有固定的内存上限,公网 IP 地址在启动几分钟内就可能遭到扫描,而且 Docker 会持续写入 root 文件系统,直到磁盘空间耗尽。

在小型服务器上,大多数问题都由以下 4 个差异导致:

  • 内存有限,内核会通过终止进程来处理内存不足。
  • 已发布的端口会直接绕过 UFW(uncomplicated firewall),因为 Docker 会自行写入防火墙规则。
  • 除非您提前配置,否则容器不会在重启后自动恢复。
  • 镜像、容器、卷和构建缓存会不断增长,直到磁盘空间耗尽。

下面的每个部分都会说明故障、您实际会看到的错误信息,以及深入解决该问题的指南。如果您还没有编写 compose 文件,请先阅读 VPS 上的 Docker Compose 基础,然后再回来继续。本页假定您已经能够启动一个堆栈。

Docker 容器使用多少 RAM?

通常比大多数人预期的少。容器是 cgroup(控制组)中的一个进程,而不是虚拟机,因此没有客户机内核,也没有固定的内存分配。实际开销取决于容器内进程访问的内容。因此,使用虚拟机构建的同一套服务可能无法放入 2 GB,而容器化的完整技术栈却可以。

下面的数据是在 Ubuntu 24.04 上使用默认配置运行标准镜像时的典型空闲值,通过启动几分钟后的 docker stats 读取。这些数据可用于初步规划,但不能代表您的工作负载。信任任何数据(包括下面的数据)之前,请先在自己的主机上运行 docker stats --no-stream

ChartTypical idle container memory, stock images, megabytes
The data behind this chart
[
  {
    "label": "nginx",
    "idle_mb": 8,
    "budget_mb": 64
  },
  {
    "label": "Redis 7",
    "idle_mb": 12,
    "budget_mb": 128
  },
  {
    "label": "Traefik v3",
    "idle_mb": 40,
    "budget_mb": 128
  },
  {
    "label": "PostgreSQL 16",
    "idle_mb": 45,
    "budget_mb": 512
  },
  {
    "label": "Uptime Kuma",
    "idle_mb": 95,
    "budget_mb": 256
  },
  {
    "label": "MariaDB 11",
    "idle_mb": 190,
    "budget_mb": 512
  },
  {
    "label": "Nextcloud (Apache)",
    "idle_mb": 210,
    "budget_mb": 768
  }
]

这两列的用途不同。idle_mb 表示容器空闲时的内存使用量。budget_mb 表示规划时应预留的内存,因为实际使用不会一直处于空闲状态。PostgreSQL 空闲时约使用 45 MB,但连接、排序和缓存开始工作后需要 512 MB。请根据预算列进行规划,根据空闲列进行调试。

注意这 7 行数据的分布。nginx 空闲时使用 8 MB,Nextcloud 使用 210 MB。位于应用前面的代理几乎不占用内存。为主机确定内存容量时,主要应考虑数据库和 PHP 应用。

关于 docker stats,需要注意一点:该内存数据包含容器读取自身文件时带入的页缓存,因此容器启动后数值会持续上升一段时间,然后趋于稳定。在判断是否存在内存泄漏前,请先监控一小时。

VPS 规格选择:2 GB、4 GB 和 8 GB 能容纳什么

首先扣除主机自身占用的内存。内核、systemd、journald、sshd 和 Docker daemon 都与容器共享同一块 RAM,dockerd 配合 containerd 会占用约 100 MB。还需要为页缓存,以及执行镜像构建或数据库转储时的内存峰值预留空间。

ChartRAM budget by plan size, megabytes
The data behind this chart
[
  {
    "plan": "2 GB VPS",
    "total_mb": 2048,
    "host_reserve_mb": 768,
    "container_mb": 1280
  },
  {
    "plan": "4 GB VPS",
    "total_mb": 4096,
    "host_reserve_mb": 1024,
    "container_mb": 3072
  },
  {
    "plan": "8 GB VPS",
    "total_mb": 8192,
    "host_reserve_mb": 1536,
    "container_mb": 6656
  }
]

host_reserve_mb 用于操作系统、Docker daemon,以及确保服务器在负载下仍能正常响应的余量。剩余部分是 container_mb,这就是唯一可分配的内存。该预留量会随规格增加:从最小规格的 768 MB 增加到最大规格的 1536 MB。这是因为更大的服务器会运行更多容器、写入更多日志,并需要更多页缓存。

2 GB 规格可为容器提供 1280 MB。为 PostgreSQL 分配 512 MB,再为 Traefik 分配 128 MB,已经用掉一半。剩余内存只能运行两个各约 256 MB 的小型应用。这是一台实际可用的服务器,但没有足够空间再运行 Nextcloud 和搜索集群。

4 GB 规格可提供 3072 MB,足以同时运行数据库、反向代理、三个应用和一个监控容器。这是运行重要服务时值得采用的最小规格,因为额外的空闲内存可以吸收一次有问题的部署。

8 GB 规格在总计 8192 MB 中可为容器提供 6656 MB。此时瓶颈通常会从内存转移到 CPU 或磁盘吞吐量。如果计算结果表明堆栈无法容纳,请直接购买更大规格,不要试图通过调优勉强运行:VPS 的实际成本说明了额外内存每月值多少钱。

两条规则可以让计算结果保持准确。为每个服务设置内存限制,避免单个失控进程拖垮整台服务器。还要保留一部分预算,不要全部分配出去,因为 docker compose buildpg_dump 都会在最需要内存时争用内存。Docker Compose 中的内存限制介绍了具体语法和常见陷阱。

为什么我的容器会以代码 137 退出?

因为内核终止了它。137 等于 128 加 9,信号 9 是 SIGKILL。容器请求的内存超过了允许使用的上限,因此 OOM killer 终止了它。

CONTAINER ID   IMAGE         STATUS
9f2c1a4d7b3e   postgres:16   Exited (137) 4 minutes ago

请确认原因,不要猜测:

docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory"

"OOMKilled": true 表示容器触及了自身的 cgroup 限制,内核日志会记录它选择终止的进程:

Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kB

这是较好的情况,因为影响只限于一个容器。较差的情况是容器完全没有限制。没有限制时,容器的上限就是整台机器的内存,因此一个服务发生内存泄漏就会耗尽主机资源,内核随后会在整个系统中按进程大小选择终止对象。日志行会失去 Memory cgroup 前缀,并显示为 Out of memory: Killed process 2417 (postgres)。它选择的进程通常是数据库,而发生内存泄漏的容器仍会继续运行。因此,为每个服务设置限制比精确确定任何单个限制的值更重要。

Swap 会改变发生时间,但不会改变计算方式。大多数 VPS 镜像都没有配置 Swap。使用 swapon --show 检查;没有 Swap 时,该命令不会输出任何内容。Swap 文件可以让内核将不常用的内存页移出,从而争取几分钟来发现问题。

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h

此时 free -h 应在 Swap 行显示非零总量。Swap 不会增加 RAM。持续承受内存压力的主机会变慢,直到您无法通过 SSH 登录修复问题。因此,应将 Swap 视为告警缓冲区,并解决容量配置问题。

为什么 UFW 不会阻止 Docker 发布的端口?

因为流量根本不会到达 UFW 保护的链。使用 -p 5432:5432 发布端口,或在 compose 中使用 ports: 条目时,daemon 会在 nat 表中写入 DNAT(目标网络地址转换)规则,并在自身的 DOCKER 链中写入 accept 规则。发往容器的数据包会被转发到容器,而不是交付给主机,因此会经过 FORWARD 路径,不会经过 UFW 写入的 INPUT 规则。

您可以在服务器上观察这一过程:

sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432

UFW 可能会显示 5432 DENY IN Anywhere,但 nat 表中同时存在针对同一端口的 DNAT tcp ... to:172.18.0.2:5432 规则。从另一台机器上,nc -vz your.server.ip 5432 仍然可以建立连接。数据库已暴露在公网中,但防火墙却显示它未被阻止。

解决方法是减少发布的端口。同一 compose 项目中的容器共享网络,并可通过服务名称相互访问。因此,只为旁边的应用提供服务的数据库根本不需要 ports: 条目。如果确实需要本机访问,请将发布绑定到 loopback:

services:
  db:
    image: postgres:16
    ports:
      - "127.0.0.1:5432:5432"

执行 docker compose up -d 后,外部的 nc -vz your.server.ip 5432 会失败,但主机上的 psql -h 127.0.0.1 -p 5432 仍然有效。在一个配置合理的小型服务栈中,只有反向代理发布端口,即 80 和 443。为什么 Docker 发布的端口会绕过 UFW介绍了必须发布端口但仍需对其进行过滤时如何使用 DOCKER-USER 链;UFW 防火墙基础介绍了底层的主机规则。

重启后我的容器为什么消失了?

因为没有任何配置要求它们恢复运行。除非设置重启策略,否则容器创建时使用 no。因此,重启会使容器保持停止状态,daemon 也不会处理它们。VPS 并不罕见需要重启:无人值守升级中的内核更新、服务商维护,以及前文所述的 OOM 过程,最终都可能导致重启。

必须同时满足两个条件。daemon 必须在启动时运行:

systemctl is-enabled docker

在标准 Ubuntu 安装中,该命令会输出 enabled。然后,每个服务都需要设置重启策略:

services:
  app:
    image: ghcr.io/example/app:1.4
    restart: unless-stopped

unless-stopped 会在重启后恢复容器运行,并尊重您主动停止的容器。always 还会在 daemon 重启时恢复那些由您主动停止的容器,这会在调试过程中造成意外。仅编辑文件还不够,因为重启策略是在创建容器时设置的。运行 docker compose up -d 重新创建容器,然后检查当前值:

docker inspect my-app | grep -A3 RestartPolicy

然后主动重启服务器,并在项目目录中运行 docker compose ps。能够在计划内重启后恢复的堆栈,也能在意外重启后恢复。如果堆栈需要保证启动顺序,或需要在启动时运行一次性任务,systemd 单元是更合适的工具:在启动时启动 Docker Compose包含该单元文件。若要确认恢复运行的容器确实提供服务,请添加 Compose 健康检查

为什么我的 VPS 磁盘已满?

因为 Docker 会一直保留所有内容,除非您明确要求它清理。您曾经拉取过的每个镜像标签、每个已停止的容器、重建时遗留的每个匿名卷,以及每一层构建缓存都会留在磁盘上。对于 40 GB 或 80 GB 的 root 文件系统(这些套餐规格很常见),磁盘空间可能在几个月内耗尽并导致服务中断,而不是几年后才出现问题。

磁盘已满时,表现不一定像崩溃。您可能会在同一个小时内从容器、apt、journald 和 docker pull 收到 no space left on device。PostgreSQL 会停止接受写入。服务器本身仍然在线,因此比反复重启更难及时发现。

删除前先检查:

docker system df
df -h /

docker system df 会将总占用拆分为镜像、容器、本地卷和构建缓存,并在每项旁边显示 RECLAIMABLE 列。在自行构建镜像的服务器上,构建缓存通常占用最多空间。

docker image prune -a
docker builder prune
docker system df

docker image prune -a 会删除没有任何容器使用的所有镜像。docker builder prune 会清除构建缓存。服务运行期间执行这两个操作通常是安全的,因为正在使用的内容会被跳过。docker system prune --volumes 则不安全,因为它会删除当前没有任何容器引用的所有卷。您为周末停止的堆栈正是这种情况,其数据库卷也会被一并删除。在输入该标志前,请先阅读绑定挂载与命名卷,并先创建备份。

容器日志增长得更不容易察觉。默认的 json-file 驱动没有大小限制,因此某个日志输出频繁的容器可能会将数 GB 数据写入 /var/lib/docker/containers。请在 /etc/docker/daemon.json 中为每个容器设置上限:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

使用 sudo systemctl restart docker 应用此设置。该命令会重启容器,因此请在合适的时间执行。该上限只对设置变更后创建的容器生效,因此请使用 docker compose up -d --force-recreate 重建正在运行的容器,然后确认:

docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containers

inspect 输出中应显示已设置 max-size。如果该字段为空,说明该容器创建于设置变更之前,仍会在没有限制的情况下写入日志。

保持小型 Docker 主机健康的习惯

这些操作不需要仪表板,也不需要学习额外工具。

  • 每月1日运行 docker system dfdf -h /。只需执行两个命令、花费30秒,就能在问题导致中断前很久看出趋势。
  • 为每个服务设置内存限制,包括那些您确定占用很小的服务。这样,主机范围的中断会变成只需重启一个容器的问题。
  • 从其他位置监控主机,这样您能在内核采取措施前发现内存或磁盘压力。Uptime Kuma 在容器中运行,空闲时内存占用约为 95 MB。
  • 备份卷,而不是容器。容器可以丢弃,卷不能。VPS 上的 restic 备份涵盖备份计划和恢复测试。
  • 在 compose 文件中固定镜像标签,并在您选择的日期更新它们。使用 latest 时,下一次 docker compose pull 获取的版本就是当天发布的版本。

只要4个数值保持在合理范围内,运行 Docker 的小型 VPS 就能保持多年稳定:内存预算、已发布端口列表、每个服务的重启策略,以及可用磁盘空间。其他方面与您已经在家中运行的 Docker 相同。

FAQ

运行 Docker 的 VPS 需要多少 RAM?

Docker 本身占用很少。daemon 和 containerd 合计约占 100 MB,其余需求取决于容器。先为主机预留资源:在 2048 MB 的主机上,为操作系统、daemon 和余量预留 768 MB,剩余 1280 MB 可供容器使用。一个占用 512 MB 的数据库、一个占用 128 MB 的反向代理和两个小型应用可以在此配置下运行。请使用 docker stats --no-stream 测量自己的服务栈,不要直接相信公开发布的数值。

我可以在 1 GB 的 VPS 上运行 Docker 吗?

可以运行 1 或 2 个轻量容器,但请在启动前先添加 swap 文件。操作系统和 Docker daemon 运行后,1 GB 主机大约有一半内存会被占用,剩余空间足以运行一个小型应用和反向代理,但不足以支撑实际负载下的数据库。在这种配置的主机上构建镜像可能失败,或导致其他服务被终止,因此应在其他主机上构建,再拉取构建完成的镜像。

UFW 能保护 Docker 容器吗?

不能保护发布到主机的端口。Docker 会写入自己的 DNAT 和转发规则,因此,发往已发布容器端口的数据包会被转发到容器,而不是交给主机处理;UFW 管理的 INPUT 规则不会看到这些数据包。ufw deny 5432 处于启用状态时,该端口仍可能从互联网访问。使用 127.0.0.1:5432:5432 将端口发布到 loopback,不发布内部服务,或在 DOCKER-USER 链中进行过滤。

VPS 重启后我的容器会自动重启吗?

只有在创建容器时设置了 restart policy 才会自动重启。为每个服务设置 restart: unless-stopped,运行 docker compose up -d,使容器按该策略重新创建,并确认 systemctl is-enabled docker 输出 enabled。然后主动重启 VPS,再检查 docker compose ps。从未测试过的 restart policy 不能视为可靠的重启策略。

应多久清理一次 Docker 镜像?

对于大多数小型服务器,每月清理一次即可;如果 docker system df 报告了你需要回收的可回收空间,也可以在此时清理。服务运行期间,docker image prune -adocker builder prune 都是安全的,因为正在使用的镜像和缓存会被跳过。除非你明确知道哪些卷未被引用,否则不要使用 docker system prune --volumes,因为它会删除当前停止的服务栈中的数据。