Appearance
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。
| 項目 | 選擇 |
|---|---|
| VPS | Hetzner CPX22 Singapore / Vultr Tokyo / DigitalOcean Singapore |
| 部署方式 | 直接跑 docker-compose |
| Reverse proxy | Caddy / Traefik,或 Cloudflare Tunnel |
| TLS | Caddy 自動申請 Let's Encrypt,或 CF 全自動 |
| DB | Postgres container(建議從 SQLite 升級) |
| CDN / WAF / DDoS | Cloudflare 免費版 |
| 月支出 | 約 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 Cert | CF 全自動 |
| 延遲 | 1 跳到 origin | 多 1 跳(通常 <10ms) |
| 失效模式 | CF 邊緣壞了還能 bypass | cloudflared daemon 死了服務就斷 |
| Cloudflare Access (SSO) | 可整合但設定較繁 | 原生支援 |
| 多 hostname / port 暴露 | 需自行設定 nginx/Caddy | Tunnel 設定檔一行搞定 |
結論:賣給第三方選 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 SSD | DB >5 GB 或同時在線使用者增多 |
服務商比較(從台灣使用):
| 供應商 | 規格 | 月費 | 延遲(台灣) | 流量 | 備註 |
|---|---|---|---|---|---|
| Hetzner CPX22 Singapore | 2 vCPU AMD / 4 GB / 80 GB | ~$9–10 | 50–70ms | 0.5 TB | CP 值最高 |
| Vultr Tokyo Regular/HF | 2 vCPU / 4 GB / 80 GB | $12–18 | 30–50ms | 2 TB+ | 延遲最低 |
| DigitalOcean Singapore | 2 vCPU / 4 GB / 80 GB | $24 | 50–70ms | 4 TB | UI/文件最佳 |
建議:無特殊需求選 Hetzner CPX22 Singapore;對延遲敏感的功能多選 Vultr Tokyo。
踩雷清單
- 加 swap:Hetzner 預設不給,需自建 2–4 GB swap file。OOM kill Postgres 是最嚴重的情況,swap 可以救命。
- 不要在 prod 機 build image:build 過程吃大量記憶體,會搞掛線上服務。正確做法:用 GitHub Actions build → push 到 GHCR / Docker Hub,VPS 只負責
docker pull+up -d。 - 磁碟先選 40 GB:大多數 VPS 支援線上擴容,但很多家不能縮回去,先保守選擇。
- SQLite → Postgres:賣給第三方前務必切換。Postgres 的備份、並發與未來擴充都遠優於 SQLite。
- Backup 算外存:
pg_dump+ gzip 丟 Cloudflare R2(10 GB 免費額度、出口流量免費)。 - Log rotation:Docker container log 若不限制大小,長期下來會吃爆磁碟。
- 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/月) |
| Swap | 2–4 GB swap file |
| 預估月費 | NT$300–400(VPS)+ domain |
Postgres 調教(4 GB RAM 機器)
ini
shared_buffers = 1GB
effective_cache_size = 2GB
work_mem = 8MB # 視查詢複雜度可提升至 16MBDocker 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 後的代碼,寫入 .env 後 docker 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相關概念
- Cloudflare Tunnel x Synology NAS 架構指南 — Cloudflare Tunnel 的詳細設定與原理(NAS 場景,運作機制相同)
- Caddy 靜態檔案伺服器 — 可作為 React 靜態檔案的反向代理與服務層,取代 nginx
- Watchtower — Docker 容器自動更新 — 可搭配本方案實現 Docker image 自動拉取更新
- MariaDB Helm 部署(Kubernetes) — 若未來服務規模成長需遷移至 K8s 環境的資料庫部署參考