SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

VPS 上執行 Docker,實際會有哪些差異?

VPS 上的 Docker 仍使用相同 engine,但 RAM 會耗盡、發布的連接埠可能繞過 UFW,容器重開機後不會恢復,磁碟也會持續被填滿。

在 VPS 上執行 Docker 時的差異

VPS 上的 Docker 使用與筆記型電腦相同的 engine 與 images,因此你已熟悉的所有指令仍可正常運作。差異在於周邊資源。筆記型電腦通常有閒置記憶體、不會被掃描的防火牆,以及大到不必查看的磁碟空間。租用的伺服器則有固定的記憶體上限。公開 IP 位址在開機後幾分鐘內就可能遭到掃描。Docker 也會持續使用 root filesystem,直到磁碟空間耗盡。

在小型主機上,以下 4 項差異最容易造成問題:

  • 記憶體有限。核心會透過終止程序來處理記憶體不足。
  • 發布的連接埠會直接繞過 UFW (uncomplicated firewall),因為 Docker 會自行寫入防火牆規則。
  • 除非事先設定,容器不會在重新開機後自動恢復。
  • images、containers、volumes 與 build cache 會持續增長,直到磁碟空間耗盡。

以下各節會說明問題、實際看到的錯誤字串,以及深入介紹修正方式的指南。如果你尚未撰寫 compose file,請先閱讀 VPS 上的 Docker Compose 基礎,再回到這裡。本頁假設你已能啟動整個 stack。

Docker 容器會使用多少 RAM?

比多數人預期的少。容器是 cgroup(control group)中的程序,不是虛擬機器,因此沒有 guest kernel,也沒有固定的記憶體配置。實際成本取決於容器內程序存取的資源。這也是為什麼完整服務堆疊使用 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,請注意一點:記憶體數值包含容器自行讀取檔案時載入的 page cache,因此容器啟動後會先上升一段時間,之後才趨於穩定。請監控一小時,再判斷是否有記憶體洩漏。

VPS 規模評估:2 GB、4 GB 與 8 GB 能容納哪些服務

先從總記憶體中扣除主機本身的需求。kernel、systemd、journald、sshd 與 Docker daemon 都與容器共用同一份 RAM,而 dockerd 搭配 containerd 約占 100 MB。你也需要保留記憶體供 page cache 使用,以及在執行 image build 或 database dump 時因瞬間負載增加所需的空間。

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,因為較大的主機會執行更多容器、寫入更多日誌,也需要更多 page cache。

2 GB 方案會為容器留下 1280 MB。若其中 512 MB 用於 PostgreSQL,128 MB 用於 Traefik,已有一半記憶體用完。剩下的空間可容納 2 個各約 256 MB 的小型應用程式。這是一台實用且能正常運作的伺服器,但沒有足夠空間再加入 Nextcloud 與搜尋叢集。

4 GB 方案會留下 3072 MB,可同時容納資料庫、反向代理、3 個應用程式與監控容器。這是值得用於重要服務的最小規模,因為預留記憶體能吸收部署失敗造成的額外負載。

8 GB 方案會從 8192 MB 中留下 6656 MB,而限制通常會從記憶體轉移到 CPU 或磁碟吞吐量。若計算結果顯示你的服務堆疊無法容納,應直接購買更大的方案,不要勉強調校:VPS 的實際成本說明每月增加的 GB 值得多少費用。

以下 2 項規則可讓計算結果保持準確。為每項服務設定記憶體限制,避免單一失控的程序拖垮整台主機。並且不要用滿預算上限,因為 docker compose buildpg_dump 都會在最需要的時候要求更多記憶體。Docker Compose 的記憶體限制說明設定語法與常見陷阱。

為什麼我的 container 會以 code 137 結束?

因為 kernel 將它終止了。137 是 128 加上 9,而 signal 9 是 SIGKILL。container 要求的記憶體超過允許上限,因此 out of memory (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 表示 container 已達到自身的 cgroup limit,而 kernel log 會列出它選擇的 process:

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

這是較好的情況,因為影響只侷限在一個 container。較差的情況是 container 完全沒有 limit。沒有 limit 時,上限就是整台機器的記憶體,因此某個 service 的 memory leak 會耗盡 host 資源,接著 kernel 會在整個 system 中依記憶體用量選擇受害者。此時 log line 會失去 Memory cgroup 前綴,並顯示為 Out of memory: Killed process 2417 (postgres)。kernel 選中的 process 通常是你的 database,而發生 memory leak 的 container 仍會繼續執行。因此,為每個 service 設定 limit,比精確決定任何單一 limit 的數值更重要。

Swap 會改變發生時間,但不會改變計算方式。大多數 VPS image 預設沒有 swap。使用 swapon --show 檢查;沒有 swap 時完全不會輸出任何內容。swap file 能讓 kernel 暫時存放不常使用的 page,爭取幾分鐘讓你察覺問題。

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 table 寫入 DNAT(目的地網路位址轉換)規則,並在自己的 DOCKER chain 寫入 accept 規則。指向 container 的封包會被轉送到該 container,而不是交付給主機,因此會經過 FORWARD 路徑,不會通過 UFW 寫入的 INPUT 規則。

您可以在伺服器上觀察這個過程:

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

即使 nat table 中針對相同埠已有 DNAT tcp ... to:172.18.0.2:5432 規則,UFW 仍可能顯示 5432 DENY IN Anywhere。從另一台機器執行 nc -vz your.server.ip 5432 仍然可以連線。資料庫實際上已暴露在公開網際網路上,但防火牆卻顯示該埠未開放。

解決方式是減少發布的埠。相同 compose project 中的 container 共用 network,並可透過 service name 互相連線,因此只供旁邊 application 使用的 database 完全不需要 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 仍可運作。在健全的小型 stack 中,只有 reverse proxy 會發布埠,通常是 80 和 443。為什麼 Docker 已發布的埠會繞過 UFW 說明在必須發布埠且仍需進行過濾時,如何使用 DOCKER-USER chain;UFW 防火牆基礎則說明底層的主機規則。

重新開機後,為什麼我的容器不見了?

因為沒有任何設定要求它們重新啟動。建立容器時,若未指定重新啟動原則,預設會使用 no,因此重新開機後容器會保持停止狀態,daemon 也不會處理。VPS 不會很少重新開機:unattended upgrades 的核心更新、供應商維護,以及前述的 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。能在計畫性重新開機後持續運作的 stack,也能應對非預期的重新開機。如果 stack 需要確保啟動順序,或需要在開機時執行一次性工作,systemd unit 會是更適合的工具:在開機時啟動 Docker Compose 包含 unit 檔案。若要確認重新啟動的容器確實提供服務,請加入 Compose healthcheck

為什麼我的 VPS 磁碟已滿?

因為 Docker 會保留所有內容,直到你明確要求它清除為止。你曾經拉取的每個 image tag、每個已停止的 container、重新建立服務後遺留的每個匿名 volume,以及每個 build cache layer,都會持續占用磁碟空間。對於 40 GB 或 80 GB 的 root filesystem,這在這類方案中很常見,幾個月內就可能造成服務中斷,而不是幾年後才發生。

磁碟已滿時,表面上不一定像是當機。在同一個小時內,你可能會從 container、no space left on device、journald 及 apt 收到 docker pull。PostgreSQL 會停止接受寫入。主機仍然在線,因此比反覆重新開機更難察覺。

刪除前先查看:

docker system df
df -h /

docker system df 會將總量分為 images、containers、local volumes 和 build cache,並在每一項旁邊顯示 RECLAIMABLE 欄位。對於自行建立 images 的主機,build cache 通常是占用空間最大的項目。

docker image prune -a
docker builder prune
docker system df

docker image prune -a 會移除沒有任何 container 使用的所有 images。docker builder prune 會清除 build cache。服務執行期間執行這兩項操作通常是安全的,因為正在使用的內容會被略過。危險的是 docker system prune --volumes,它會刪除目前沒有任何 container 參照的所有 volumes。你為週末停止的 stack 正好可能符合這種情況,其 database volume 也會一併刪除。執行該 flag 前,先閱讀 bind mounts 與 named volumes 的比較,並先建立備份。

Container logs 是較不易察覺的成長來源。預設的 json-file driver 沒有大小限制,因此某個訊息過多的 container 可能會將數 GB 的資料寫入 /var/lib/docker/containers。請在 /etc/docker/daemon.json 中為每個 container 設定上限:

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

使用 sudo systemctl restart docker 套用設定。這會重新啟動你的 containers,因此請選擇適當時機執行。此上限只會套用到變更後建立的 containers,因此請使用 docker compose up -d --force-recreate 重新建立目前執行中的 containers,然後確認:

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

inspect 輸出應顯示已設定 max-size。如果該欄位是空的,表示這個 container 在變更前就已建立,仍會在沒有上限的情況下寫入資料。

讓小型 Docker 主機維持健康的習慣

這些工作都不需要儀表板或必須另外學習的工具。

  • 每月1日執行 docker system dfdf -h /。只需執行兩個指令、花費30秒,就能在問題演變成服務中斷前,及早看出趨勢。
  • 為每項服務設定記憶體限制,包括你確定規模很小的服務。限制能將整台主機的中斷,縮小為單一容器重新啟動。
  • 從其他位置監控主機,讓你在核心採取動作前,就能得知記憶體或磁碟壓力。Uptime Kuma 可在容器中執行,閒置時約使用 95 MB。
  • 備份 volume,不要備份容器。容器可以丟棄,volume 則不能。VPS 上的 restic 備份涵蓋排程與還原測試。
  • 在 compose 檔案中固定映像標籤,並在你指定的日期更新。使用 latest 時,下一次 docker compose pull 取得的版本,就是當天早上發布的版本。

當4個數值維持在合理範圍內時,執行 Docker 的小型 VPS 可以多年保持穩定:記憶體預算、已發布的連接埠清單、每項服務的 restart policy,以及可用磁碟空間。其他部分都與你已經在家中執行的 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 嗎?

可以,但建議只執行一或兩個負載較低的容器,並在開始前先建立 swap 檔案。1 GB 主機在作業系統與 Docker daemon 啟動後,大約會用掉一半記憶體,因此還能容納一個小型應用程式與反向代理,但無法支撐實際負載下的資料庫。在這種規模的主機上建置映像檔可能會失敗,或導致其他服務被終止,因此應在其他主機上建置,再拉取完成的映像檔。

UFW 能保護 Docker 容器嗎?

無法保護你發布的連接埠。Docker 會自行寫入 DNAT 與轉送規則,因此指向已發布容器連接埠的封包會被轉送到容器,而不是交給主機處理;UFW 管理的 INPUT 規則不會看到這些封包。即使 ufw deny 5432 正在運作,該連接埠仍可能從網際網路回應。請使用 127.0.0.1:5432:5432 將服務發布到 loopback,讓內部服務維持未發布狀態,或在 DOCKER-USER chain 中進行過濾。

VPS 重新開機後,容器會自動重新啟動嗎?

只有在建立容器時設定 restart policy 才會。請在每個服務上設定 restart: unless-stopped,執行 docker compose up -d,讓容器以該設定重新建立,並確認 systemctl is-enabled docker 輸出 enabled。接著刻意重新開機,再檢查 docker compose ps。未經測試的 restart policy 不算是可靠的 restart policy。

應該多久清理一次 Docker 映像檔?

對大多數小型伺服器而言,每月一次即可;如果 docker system df 顯示有可回收、且你需要釋放的空間,也可以立即清理。服務執行期間,docker image prune -adocker builder prune 都很安全,因為正在使用的映像檔與快取會被略過。除非你確定哪些 volume 未被參照,否則請避免使用 docker system prune --volumes,因為它會刪除任何當時停止之服務組合的資料。