VPS 上的 Docker 到底有何不同?
Docker engine 和 images 不變,但 VPS 的 RAM、UFW 埠規則、重開機復原與磁碟空間都更容易出問題,本文整理 4 個常見差異與處理方式。
在 VPS 上執行 Docker 時的差異
VPS 上的 Docker 使用與筆記型電腦相同的 engine 和 images,因此你熟悉的每個指令仍可正常運作。差異在於周邊資源。筆記型電腦通常有多餘的記憶體、防火牆,以及大到不需特別關注的磁碟空間。租用的伺服器則有固定的記憶體上限、開機數分鐘內就會遭到掃描的公開 IP 位址,以及 Docker 未經確認就會持續填滿的 root filesystem。
在小型伺服器上,以下 4 項差異最容易造成問題:
- 記憶體有限,kernel 會透過終止程序來處理記憶體不足。
- 發布的埠會直接通過 UFW(uncomplicated firewall),因為 Docker 會自行寫入防火牆規則。
- 除非事先設定,否則容器不會在重新開機後自動恢復。
- images、containers、volumes 和 build cache 會持續增加,直到磁碟空間用盡。
以下每個章節都會說明故障現象、你實際會看到的訊息,以及深入解決該問題的指南。如果你尚未撰寫 compose file,請先閱讀 VPS 上的 Docker Compose 基礎,再回到這裡。本頁假設你已能啟動 stack。
Docker 容器會使用多少 RAM?
通常比大多數人預期的少。容器是 cgroup(control group)中的程序,不是虛擬機器,因此沒有客體核心,也沒有固定的記憶體配置。實際成本取決於容器內程序存取的資源。這就是為什麼完整的服務堆疊可以容納在 2 GB 記憶體中,而使用虛擬機器建置的相同堆疊卻無法做到。
以下數值是 Ubuntu 24.04 使用預設設定執行標準映像時的典型閒置用量,於啟動幾分鐘後從 docker stats 讀取。這些數值適合用於初步規劃,不代表你的工作負載效能。信任任何數值前,包括以下數值,請先在自己的主機上執行 docker stats --no-stream。
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。你也需要保留記憶體給頁面快取,以及執行映像檔建置或資料庫傾印時的突增用量。
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 給容器。其中使用 512 MB 執行 PostgreSQL,再使用 128 MB 執行 Traefik,已經用掉一半。剩餘記憶體可供兩個各約 256 MB 的小型應用程式使用。這是一台實用且能執行實際工作的伺服器,但沒有多餘空間再加入 Nextcloud 與搜尋叢集。
4 GB 方案會留下 3072 MB,可同時容納資料庫、反向代理、3 個應用程式與監控容器。這是值得用於重要服務的最小規模,因為剩餘記憶體可以吸收部署不當造成的額外負載。
8 GB 方案會從 8192 MB 中留下 6656 MB,限制通常會從記憶體轉移到 CPU 或磁碟吞吐量。有一類容器會依設定而非負載決定記憶體大小:本機模型伺服器會依內容視窗大小保留 KV cache,因此在收到第一個請求前,調高 Ollama 的 num_ctx 就可能讓預算增加數 GB。如果計算結果顯示堆疊無法容納,請直接購買較大的方案,不要勉強調整設定來因應:VPS 實際需要多少成本 說明了每月增加的 GB 價值。
有兩項規則可讓計算保持準確。為每個服務設定記憶體上限,避免單一失控程序拖垮整台主機。並且不要用盡預算上限,因為 docker compose build 與 pg_dump 都會在最需要的時刻使用記憶體。Docker Compose 的記憶體限制 說明語法與常見陷阱。
為什麼我的容器會以代碼 137 結束?
因為 kernel 將它終止了。137 是 128 加上 9,而 signal 9 是 SIGKILL。容器要求的記憶體超過允許上限,因此 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 表示容器達到自身的 cgroup 限制,而 kernel log 會記錄它選定的 process:
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kB這是較好的情況,因為影響只會停止在單一容器。較糟的情況是容器完全沒有設定限制。沒有限制時,上限就是整台機器的記憶體,因此單一服務的 memory leak 會耗盡 host 資源,接著 kernel 會在整個系統中依 process 大小選擇受害者。此時 log 行會失去 Memory cgroup 前綴,並顯示 Out of memory: Killed process 2417 (postgres)。它選到的 process 通常是你的 database,而發生 memory leak 的容器仍會繼續執行。因此,為每個服務設定限制,比精確決定任何單一限制的數值更重要。
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 -hfree -h 現在應會在 Swap 列顯示非零的總量。Swap 不會增加 RAM。持續承受記憶體壓力的主機會變慢到無法透過 SSH 登入修復,因此應將 swap 視為警示緩衝,並修正資源配置。
為什麼 UFW 不會封鎖已發布的 Docker 埠?
因為流量根本不會到達 UFW 防護的鏈。使用 -p 5432:5432 發布埠,或在 compose ports: 項目中發布埠時,daemon 會在 nat 表中寫入 DNAT(目的地網路位址轉譯)規則,並在專用的 DOCKER 鏈中寫入允許規則。指向容器的封包會被轉送到該容器,而不是交付給主機,因此會經過 FORWARD 路徑,不會通過 UFW 寫入的 INPUT 規則。
您可以在伺服器上觀察這個過程:
sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432UFW 可能會顯示 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 防火牆基礎則說明底層的主機規則。
重新開機後,為什麼我的容器不見了?
因為沒有設定任何機制讓它們重新啟動。除非另行設定,容器建立時的 restart policy 是 no,因此重新開機後容器會維持停止狀態,daemon 也不會處理。VPS 不時會重新開機:unattended upgrades 安裝 kernel 更新、供應商進行維護,以及前述的 OOM 流程,都可能導致重新開機。
必須同時滿足兩個條件。daemon 必須在開機時啟動:
systemctl is-enabled docker在標準 Ubuntu 安裝中,這會輸出 enabled。接著,每個服務都需要設定 restart policy:
services:
app:
image: ghcr.io/example/app:1.4
restart: unless-stoppedunless-stopped 會在重新開機後重新啟動容器,也會尊重你刻意停止的容器。always 則會在 daemon 重新啟動時,連你刻意停止的容器也一併重新啟動,這在除錯期間可能造成意外。只編輯檔案本身並不足夠,因為 restart policy 是在建立容器時設定的。執行 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、每個已停止的容器、重建時遺留的每個匿名 volume,以及每一層 build cache 都會留在磁碟上。對於這類方案常見的 40 GB 或 80 GB root filesystem,幾個月內就可能耗盡空間並造成服務中斷,而不是要等上幾年。
磁碟已滿不一定看起來像當機。同一個小時內,你可能分別從容器、apt、journald 及 docker pull 收到 no space left on device。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 dfdocker image prune -a 會移除沒有任何容器使用的 image。docker builder prune 會清除 build cache。服務執行期間執行這兩項通常是安全的,因為正在使用的內容會被跳過。危險的是 docker system prune --volumes,它會刪除目前沒有任何容器參照的所有 volume。你為週末暫停的 stack 正是這種情況,其資料庫 volume 也會一併刪除。輸入該 flag 前,先閱讀 bind mounts 與 named volumes 的差異,並先建立備份。
容器日誌是較不明顯的成長來源。預設的 json-file driver 沒有大小限制,因此某個輸出頻繁的容器可能將數 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/containersinspect 輸出應顯示已設定 max-size。如果該欄位為空,表示該容器早於這項變更建立,仍會在沒有上限的情況下寫入。
讓小型 Docker 主機維持健康的習慣
這些工作都不需要儀表板,也不需要學習新的工具。
- 在每月第 1 天執行
docker system df和df -h /。兩個命令只需 30 秒,就能在問題演變成服務中斷前,及早看出趨勢。 - 為每個服務設定記憶體限制,包括你確定佔用很少的服務。這項限制可將整台主機的服務中斷,控制在單一重新啟動的容器內。
- 從其他位置監控主機,這樣就能在核心採取動作前,得知記憶體或磁碟壓力。Uptime Kuma 可在容器中執行,閒置時約佔用 95 MB。
- 備份 volume,不要備份容器。容器可拋棄,但 volume 不可。VPS 上的 restic 備份涵蓋排程與還原測試。
- 在 compose 檔案中固定 image tag,並在指定的日期更新。使用
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 檔案。作業系統與 Docker daemon 啟動後,1 GB 主機大約有一半記憶體已被使用,因此還能容納一個小型應用程式與反向代理,但不足以讓資料庫承受實際負載。在這種主機上建置映像檔可能失敗,或導致其他服務被終止。因此請在其他主機上建置,再拉取完成的映像檔。
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 -a 與 docker builder prune 都是安全的,因為正在使用的映像檔與快取會被略過。除非你確定哪些 volumes 未被參照,否則請避免使用 docker system prune --volumes,因為它會刪除任何當時停止之服務堆疊的資料。