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。
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 時因瞬間負載增加所需的空間。
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 build 與 pg_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 -hfree -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-stoppedunless-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 dfdocker 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/containersinspect 輸出應顯示已設定 max-size。如果該欄位是空的,表示這個 container 在變更前就已建立,仍會在沒有上限的情況下寫入資料。
讓小型 Docker 主機維持健康的習慣
這些工作都不需要儀表板或必須另外學習的工具。
- 每月1日執行
docker system df和df -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 -a 與 docker builder prune 都很安全,因為正在使用的映像檔與快取會被略過。除非你確定哪些 volume 未被參照,否則請避免使用 docker system prune --volumes,因為它會刪除任何當時停止之服務組合的資料。