Skip to content

React + API + DB 服務遠端部署方案

以單台 VPS + Cloudflare Tunnel 將 docker-compose 堆疊上線的最低遷移成本方案,適合賣給第三方的小型服務。

概述

當本地 docker-compose 服務(React 前端 + API 後端 + SQLite/Postgres 資料庫)需要遷移到遠端環境並商業化販售時,最核心的問題是「遷移成本 vs 維護負擔」的取捨。

三大部署路線中,**單台 VPS + Cloudflare(方案 A)**最適合現有 docker-compose 架構:無需改寫任何程式碼即可直接搬上去運行,月費約 NT$300–400,前端由 Cloudflare 免費提供 CDN/WAF/DDoS 防護。PaaS 拆分方案(方案 B)雖省去伺服器維護,但免費方案的冷啟動延遲(1–3 秒)對第三方客戶觀感較差;全 Cloudflare Workers 方案(方案 C)需要重寫後端為 Workers runtime,遷移工程不符成本效益。

在 Cloudflare 接入方式上,Cloudflare Tunnel 優於傳統 DNS Proxy 橘雲模式:Origin 伺服器 IP 完全不曝光、TLS 全自動管理、不需維護防火牆 IP allowlist,且原生支援 Cloudflare Zero Trust Access,非常適合販售給第三方的場景。

核心內容

三大部署方案比較

方案 A:單台 VPS + Cloudflare(推薦)

最接近現有 setup,遷移成本幾乎為零。以 Hetzner CPX22 Singapore 為主要選擇,直接跑 docker-compose,搭配 Caddy 或 Cloudflare Tunnel 處理反向代理與 TLS。

項目選擇
VPSHetzner CPX22 Singapore / Vultr Tokyo / DigitalOcean Singapore
部署方式直接跑 docker-compose
Reverse proxyCaddy / Traefik,或 Cloudflare Tunnel
TLSCaddy 自動申請 Let's Encrypt,或 CF 全自動
DBPostgres container(建議從 SQLite 升級)
CDN / WAF / DDoSCloudflare 免費版
月支出約 NT$200–400

方案 B:PaaS 拆分

Frontend 用 Cloudflare Pages(免費),Backend 用 Fly.io 或 Render Starter($7/月),DB 用 Neon Postgres 或 Supabase(免費版)。

優點:免維護、自動 HTTPS、GitHub auto-deploy。 缺點:多帳號管理、免費方案冷啟動 1–3 秒,對賣給第三方的觀感較差。

方案 C:全 Cloudflare(Pages + Workers + D1)

最便宜、全球延遲最低,但需將 API server 改寫為 Workers runtime,對現有 docker-compose 遷移工程過大,不值得為了省少量費用進行大規模重構。

Cloudflare 接入方式:DNS Proxy vs Tunnel

CDN / WAF / DDoS L3–L7 防護能力兩者完全一樣(流量都過 Cloudflare edge),差異在運維層面:

項目DNS + Proxy(橘雲)Cloudflare Tunnel
Origin IP 曝光⚠️ 可能被挖出來繞過 CF✅ 完全不曝光
防火牆設定需 allowlist CF IP 範圍不需開任何 inbound port
TLS 證書需自行處理 Let's Encrypt / CF Origin CertCF 全自動
延遲1 跳到 origin多 1 跳(通常 <10ms)
失效模式CF 邊緣壞了還能 bypasscloudflared daemon 死了服務就斷
Cloudflare Access (SSO)可整合但設定較繁原生支援
多 hostname / port 暴露需自行設定 nginx/CaddyTunnel 設定檔一行搞定

結論:賣給第三方選 Tunnel。 理由:IP 零曝光、TLS 全自動、後續加 Zero Trust Access 方便。延遲多的幾 ms 對 web app 完全無感。

VPS 規格選擇

資源 footprint 估算(閒置記憶體):

元件閒置記憶體
OS + Docker daemon~400 MB
Postgres(小資料量)~200–400 MB
API backend(Node/Python)~150–400 MB
Caddy / nginx(靜態 React)~30 MB
cloudflared~50 MB
基礎合計~1 GB(還需加 buffer 給 DB query、突發流量、重啟瞬間)

規格分級:

等級規格適用情境
最低可用1 vCPU / 2 GB / 40 GB SSD自己試水溫,不建議賣客戶
甜蜜點(推薦)2 vCPU / 4 GB / 40–80 GB SSD正式賣給第三方
成長期2–4 vCPU / 8 GB / 80–160 GB SSDDB >5 GB 或同時在線使用者增多

服務商比較(從台灣使用):

供應商規格月費延遲(台灣)流量備註
Hetzner CPX22 Singapore2 vCPU AMD / 4 GB / 80 GB~$9–1050–70ms0.5 TBCP 值最高
Vultr Tokyo Regular/HF2 vCPU / 4 GB / 80 GB$12–1830–50ms2 TB+延遲最低
DigitalOcean Singapore2 vCPU / 4 GB / 80 GB$2450–70ms4 TBUI/文件最佳

建議:無特殊需求選 Hetzner CPX22 Singapore;對延遲敏感的功能多選 Vultr Tokyo。

踩雷清單

  1. 加 swap:Hetzner 預設不給,需自建 2–4 GB swap file。OOM kill Postgres 是最嚴重的情況,swap 可以救命。
  2. 不要在 prod 機 build image:build 過程吃大量記憶體,會搞掛線上服務。正確做法:用 GitHub Actions build → push 到 GHCR / Docker Hub,VPS 只負責 docker pull + up -d
  3. 磁碟先選 40 GB:大多數 VPS 支援線上擴容,但很多家不能縮回去,先保守選擇。
  4. SQLite → Postgres:賣給第三方前務必切換。Postgres 的備份、並發與未來擴充都遠優於 SQLite。
  5. Backup 算外存pg_dump + gzip 丟 Cloudflare R2(10 GB 免費額度、出口流量免費)。
  6. Log rotation:Docker container log 若不限制大小,長期下來會吃爆磁碟。
  7. Uptime 監控:UptimeRobot 免費版即可,設定 5 分鐘輪詢。

關鍵要點

  • 單台 VPS + Cloudflare Tunnel 是現有 docker-compose 服務最低遷移成本的商業化路徑
  • Cloudflare Tunnel 比 DNS Proxy 更適合第三方販售:IP 零曝光、TLS 全自動、無需管防火牆 allowlist
  • 甜蜜點規格:2 vCPU / 4 GB RAM,Hetzner CPX22 Singapore CP 值最高(~$9/月)
  • 必做三件事:加 swap、設 Log rotation、SQLite 切 Postgres
  • 月費估算:NT$300–400(VPS)+ domain 費用,R2 / UptimeRobot 在免費額度內

實際應用

此方案適合將個人或小型團隊的 docker-compose 服務商業化,無需重寫任何程式碼即可上線。搭配 Cloudflare Tunnel 可在完全不開放防火牆 inbound port 的情況下對外服務,同時享有 Cloudflare 的全球 CDN 加速與 WAF 防護。後續若需要限制特定客戶存取,可直接在 Cloudflare Zero Trust 加上 Access 規則,不需修改後端程式。

部署設定參考

以下為實際部署時使用的完整設定,供日後查詢與複製使用。

環境參數

項目推薦值
VPS 規格2 vCPU / 4 GB RAM / 80 GB SSD
供應商(CP 值最高)Hetzner CPX22 Singapore(~$9–10/月)
供應商(低延遲)Vultr Tokyo(~$12–18/月)
Swap2–4 GB swap file
預估月費NT$300–400(VPS)+ domain

Postgres 調教(4 GB RAM 機器)

ini
shared_buffers = 1GB
effective_cache_size = 2GB
work_mem = 8MB       # 視查詢複雜度可提升至 16MB

Docker Log Rotation(/etc/docker/daemon.json)

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

設定後需重啟 Docker daemon:sudo systemctl restart docker

Cloudflare Tunnel 加入 docker-compose

yaml
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
    environment:
      - CLOUDFLARE_TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}

取得 token 步驟:Cloudflare Zero Trust → Networks → Tunnels → 建立 Tunnel → 選 Docker 部署 → 複製 --token 後的代碼,寫入 .envdocker compose up -d

Swap 建立指令

bash
# 建立 4GB swap file
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 永久生效(加入 /etc/fstab)
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

每日備份腳本(pg_dump + Cloudflare R2)

bash
#!/bin/bash
# /home/deploy/backup.sh
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="/tmp/db_backup_${TIMESTAMP}.sql.gz"
pg_dump -U postgres mydb | gzip > "${BACKUP_FILE}"
rclone copy "${BACKUP_FILE}" r2:my-bucket/backups/
rm "${BACKUP_FILE}"

搭配 crontab -e 設定每日凌晨 3 點執行:

0 3 * * * /home/deploy/backup.sh >> /var/log/db-backup.log 2>&1

實作 Action Items(建議順序)

1.  開 Hetzner CPX22 Singapore(2 vCPU / 4 GB / 80 GB)
2.  裝 Docker + docker-compose
3.  把 SQLite 改成 Postgres container
4.  加 Cloudflare Tunnel container 到 docker-compose
5.  Cloudflare Dashboard 建 Tunnel + 設 CNAME
6.  設定 2–4 GB swap file
7.  設定 Docker log rotation(/etc/docker/daemon.json)
8.  Postgres tuning(shared_buffers / effective_cache_size / work_mem)
9.  設 GitHub Actions:build image → push GHCR → VPS docker pull + up -d
10. pg_dump cron + rclone 丟 R2 每日備份
11. UptimeRobot 加監控
12. Cloudflare WAF 開預設規則 + Bot Fight Mode

相關概念

來源