如何用 Docker 在 VPS 自行代管 ERPNext
了解 ERPNext Docker Compose 的 11 個容器、VPS 資源、TLS、外寄郵件與版本鎖定,並完成實際測試過的備份還原,避免錯誤碼 137。
您即將執行的內容
在 VPS 上自行代管 ERPNext 是維運工作,不是執行一個指令就能完成的安裝。官方 Docker Compose 堆疊包含 11 個容器,並儲存您的總帳與客戶資料。因此,以下每個步驟都必須符合更高標準:備份必須實際還原過,才能算是備份;未鎖定版本的映像標籤,則可能隨時觸發結構描述遷移。
本文會反覆使用幾個名稱。ERPNext 是商務應用程式。Frappe 是其底層的 Python framework。Bench 是管理 sites 的命令列工具,已安裝在容器內。site 代表一個租戶:包含一個 MariaDB 資料庫,以及一個存放上傳檔案的目錄。本文幾乎所有指令都會在 backend 容器內,透過 bench,針對一個指定名稱的 site 執行。
本指南使用 frappe_docker repository,也就是專案維護的部署方式。以下每個指令都已在 2026 年 8 月針對該 repository 完成驗證。如果您不熟悉 Docker Compose,在 VPS 上執行 Docker Compose 涵蓋了本指南預設您已了解的基礎。
ERPNext 需要多少 VPS 資源?
The data behind this chart
[
{
"label": "Evaluation",
"vcpu": 2,
"ram_gb": 4,
"disk_gb": 40
},
{
"label": "Small production",
"vcpu": 4,
"ram_gb": 8,
"disk_gb": 100
},
{
"label": "Room to grow",
"vcpu": 4,
"ram_gb": 16,
"disk_gb": 160
}
]公開建議是在第一位使用者登入前,先準備 2 個 vCPU 與 4 GB RAM。這是評估等級。這些數值是起始點,不是本指南實測的結果;實際需求取決於您自己的文件量。最後一列根本不是公開的最低需求,而是大致代表記憶體不再成為主要考量的規模。
請務實看待小型方案。1 GB 或 2 GB 的 VPS 可以啟動整個堆疊,但第一次匯入資料或執行第一份耗時報表時就會失敗,因為 9 個長時間執行的容器、MariaDB 的 buffer pool,以及負責建立報表的 Python worker,無法同時容納在這麼少的記憶體中。這種失敗不會平順發生。核心的 out of memory killer 會停止某個容器,接著在 docker inspect 上顯示 "OOMKilled": true,結束代碼為 137。worker 若在工作執行期間被終止,已提交的文件會留下未完成的背景工作。
對每天使用 ERPNext 的公司而言,8 GB RAM、4 個 vCPU 與 100 GB SSD 是務實的最低配置。RAM 會最先耗盡。磁碟成長速度也比預期快,因為每個附件與每份本機備份都會儲存在與資料庫相同的磁碟區上。
11 個容器及其用途
堆疊啟動且已有 9 個容器執行後,執行 docker compose ps。另外兩個容器 configurator 與 create-site 會各自執行一次後結束,因此總數為 11 個。
backend在 gunicorn 下執行 Frappe 應用程式。bench位於此處。frontend是 nginx。它提供靜態資產,並將其他所有請求轉送至後端。queue-short與queue-long是 RQ (Redis Queue) worker。它們執行寄出電子郵件、匯入資料及產生報表等背景工作。scheduler執行依時間排程的工作,包括排程報表與自動重複文件。websocket是瀏覽器即時更新所使用的 socket.io 程序。db是 MariaDB。redis-cache與redis-queue是兩個分開的 Redis 執行個體,分別用於快取與工作佇列。
瞭解這種分工很重要,因為它能告訴你應該查看哪個日誌。卡住的電子郵件屬於佇列 worker 問題,因此 docker compose logs -f queue-short 是正確的指令。頁面可以載入,但通知徽章始終不更新,則屬於 websocket 問題。針對這兩種問題查看 backend 日誌,都會浪費大量排查時間。
使用正式版 compose 檔案安裝,不要使用示範版
此 repository 提供 pwd.yml,README 也明確說明:「此設定僅供短期評估使用。您無法在此設定中安裝自訂應用程式。」您可以用它花一個下午查看 ERPNext,但不要用它執行公司的正式業務。
sudo apt update && sudo apt install -y git
curl -fsSL https://get.docker.com | bash
git clone https://github.com/frappe/frappe_docker
cd frappe_docker
mkdir -p ~/gitops
cp example.env ~/gitops/erpnext.env開啟 ~/gitops/erpnext.env,並修改 4 個值。ERPNEXT_VERSION 固定 image tag。範例檔案中的 DB_PASSWORD 以 123 提供。SITES_RULE 是 Traefik 的路由規則,LETSENCRYPT_EMAIL 用來接收憑證警告。
ERPNEXT_VERSION=v16.32.1
DB_PASSWORD=<a long random password>
SITES_RULE=Host(`erp.example.com`)
LETSENCRYPT_EMAIL=ops@example.com現在產生一個 compose 檔案,然後啟動它。
docker compose --project-name erpnext \
--env-file ~/gitops/erpnext.env \
-f compose.yaml \
-f overrides/compose.mariadb.yaml \
-f overrides/compose.redis.yaml \
-f overrides/compose.https.yaml \
config > ~/gitops/erpnext.yaml
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml up -dconfig 不會啟動任何服務。它會合併基礎檔案與覆寫檔案,並在替換所有變數後輸出結果。接著執行這個產生的檔案。多這一步值得:執行中的 stack 會固定成一個可讀取、可提交的檔案,因此當有人編輯 env 檔案或您拉取 repository 時,設定不會在執行期間改變。多個 Docker Compose 檔案的合併方式 詳細說明覆寫規則。
等待 db 啟動,並等待 configurator 結束。這需要幾秒鐘。接著建立 site。
docker compose --project-name erpnext exec backend \
bench new-site --mariadb-user-host-login-scope=% \
--db-root-password '<your DB_PASSWORD>' \
--install-app erpnext \
--admin-password '<a strong admin password>' \
erp.example.com檢查:
docker compose --project-name erpnext ps
docker compose --project-name erpnext exec backend bench --site erp.example.com list-appslist-apps 應輸出 frappe 與 erpnext 及其版本。正常運作的 ps 會在 running 狀態顯示 9 個服務,且 restarting 中不應有任何服務。
這裡經常有 2 個問題。Docker 環境中,--mariadb-user-host-login-scope=% 不是可選設定。app container 會透過 Docker network 連線到 MariaDB,因此對資料庫而言,它是來自遠端主機;限定於 localhost 的資料庫使用者無法從該處登入。建立 site 時會失敗,並顯示 MariaDB access denied 錯誤,指出 root 使用者。% 範圍會允許新 site 的使用者從該私有 network 中的任何主機存取。
第二個問題是 site 名稱。前端預設會根據 HTTP Host header 選擇要提供的 site,因此以 erpnext 建立的 site,即使兩者都存在,也無法透過 erp.example.com 存取。請如上例使用網域名稱作為 site 名稱,或在 env 檔案中將 FRAPPE_SITE_NAME_HEADER 設為 site 名稱,然後再次產生 compose 檔案。
HTTPS,以及啟用前必須滿足的條件
compose.https.yaml override 會讓 Traefik 在 443 埠上執行,將 80 埠重新導向至該埠,並向 Let's Encrypt 申請憑證。TLS(transport layer security)可避免發票內容與工作階段 cookie 以明文形式傳輸。
必須同時滿足兩項條件,否則永遠不會核發憑證。erp.example.com 的 DNS A 記錄必須已指向 VPS。80 與 443 埠必須能從網際網路連線,因為 Let's Encrypt 會透過 80 埠上的 HTTP-01 challenge 驗證你是否控制該網域。請同時檢查供應商的網路防火牆與主機上的防火牆。兩者是分開的控制項,而控制台防火牆最容易被忽略。
憑證會寫入 cert-data volume 中的 /letsencrypt/acme.json。如果瀏覽器顯示預設憑證,而不是你的憑證,請在 docker compose --project-name erpnext ps 找出 proxy 服務名稱,並查看其日誌中的 ACME(automatic certificate management environment)錯誤。在同一台伺服器上執行其他 Web 應用程式?讓單一 Traefik instance 位於多個 Docker Compose 應用程式之前說明如何共用 proxy,避免爭用 443 埠。
對外寄信,否則發票永遠無法送出
這是大多數 ERPNext 指南略過的步驟,但它決定了系統是否實用。沒有正常運作的對外郵件,發票無法送達客戶、密碼重設郵件不會送達,排程報表也無法交付。此堆疊不包含郵件伺服器。
不要嘗試從 VPS 直接使用 port 25 寄信。大多數供應商會封鎖新帳戶的對外 port 25,而成功送出的郵件也可能遭到拒收或歸入垃圾郵件,因為新 VPS 位址沒有寄件信譽。請使用 port 587 上的通過驗證 relay。
支援的方式是使用 ERPNext 介面中的 Email Account 畫面,該畫面會以加密方式儲存密碼。你也可以將這些設定寫入 site config:
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_server smtp.example.com
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_port 587 --parse
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config use_tls 1 --parse
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_login 'erp@example.com'
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config auto_email_id 'erp@example.com'--parse 會將 587 儲存為數字,而不是字串 "587"。讀回檔案,確認這兩個值外側沒有引號:
docker compose --project-name erpnext exec backend \
cat sites/erp.example.com/site_config.json請透過 Email Account 畫面設定 mail_password,不要在 command line 上設定。這樣系統會以加密方式儲存,且不會寫入 shell history。
接著寄出一封實際郵件。建立 Sales Invoice,將其寄到你能控制的地址,並在過程中監看佇列:
docker compose --project-name erpnext logs -f queue-short對外郵件是在背景工作中處理,因此郵件未送達時,通常會在該日誌中顯示為失敗的工作,而不是瀏覽器中的錯誤。也請為寄件網域發布 SPF(sender policy framework)與 DKIM(domainkeys identified mail)記錄,然後加入 DMARC 政策。缺少這些設定時,即使發票本身正確,也可能落入客戶的垃圾郵件資料夾。如果你希望自行掌控完整流程,自架的 Mailcow 郵件伺服器可提供由你控制的 relay,並可部署在與 ERP 分開的另一台伺服器上。
可實際還原的備份
單獨的資料庫傾印不是 ERPNext 的完整備份。附件與私有檔案位於 sites 目錄,不在 MariaDB 中。只還原資料庫時,所有已上傳的採購單都會變成失效連結。
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-files這會在 sites volume 的 sites/erp.example.com/private/backups 中寫入 4 個檔案:
-database.sql.gz傾印檔-files.tar公開檔案的封存檔-private-files.tar私有檔案的封存檔-site_config_backup.jsonsite 設定檔副本
第 4 個檔案最常被丟棄,但它也是造成問題的關鍵。該檔案包含 encryption_key,這是 Frappe 用來加密儲存密碼的金鑰,包括電子郵件帳戶認證資訊、付款閘道金鑰,以及所有整合服務的 secret。還原沒有相符金鑰的資料庫後,site 仍會正常載入,但寄信會失敗,並顯示:
frappe.exceptions.ValidationError: Encryption key is invalid! Please check site_config.json務必始終將這 4 個檔案放在一起。
接著將備份移出伺服器。留在 volume 內的備份無法在伺服器故障後保留,而且 bench 也會清理這些檔案:預設會刪除該目錄中超過 24 小時的備份。
docker compose --project-name erpnext cp \
backend:/home/frappe/frappe-bench/sites/erp.example.com/private/backups \
~/erpnext-backups從 cron 執行這項工作,然後將目錄推送到不由你管理的位置。加密的 restic 異地備份 是合適的工具,因為它會在上傳前加密,而 restic check 可確認 repository 仍可讀取。ERP 備份是整本帳簿的副本,因此應以靜態加密形式儲存在不是這台伺服器的硬體上。
在需要還原之前先測試還原
未經測試的備份只是一種猜測。請將它還原到同一台主機上的第二個站台,絕對不要還原到正式站台。
docker compose --project-name erpnext exec backend \
bench new-site --mariadb-user-host-login-scope=% \
--db-root-password '<your DB_PASSWORD>' \
--admin-password '<a strong admin password>' \
restore-test.example.com
docker compose --project-name erpnext exec backend \
bench --site restore-test.example.com --force restore \
sites/erp.example.com/private/backups/<stamp>-erp.example.com-database.sql.gz \
--with-public-files sites/erp.example.com/private/backups/<stamp>-erp.example.com-files.tar \
--with-private-files sites/erp.example.com/private/backups/<stamp>-erp.example.com-private-files.tar \
--db-root-password '<your DB_PASSWORD>'將備份設定中的加密金鑰複製到還原的站台,否則整合功能仍會故障:
docker compose --project-name erpnext exec backend \
bench --site restore-test.example.com set-config encryption_key '<value from site_config_backup.json>'接著以會計人員的方式檢查還原結果。開啟應收帳款報表,將期末餘額與正式站台比較。開啟最近的採購發票,並下載其附件。站台能顯示登入頁面,完全不能證明還原成功。
完成後移除測試站台:
docker compose --project-name erpnext exec backend \
bench drop-site restore-test.example.com為什麼版本鎖定對 ERPNext 更重要
在靜態網站上,未鎖定的映像標籤代表意外重新啟動。在 ERPNext 上,這代表執行 schema migration。bench migrate 會改寫資料庫資料表,也可能改寫文件資料,而且無法復原。回復舊版必須從備份還原,不是執行 docker compose down。
因此請鎖定標籤。ERPNEXT_VERSION=v16.32.1 是 2026 年 8 月儲存庫自身 pwd.yml 中鎖定的版本。請勿未經確認就沿用這個版本號。最新版本列在 frappe/erpnext releases page,現有的映像標籤則列在 Docker Hub。升級前,請先閱讀目標版本的說明。
升級本身必須從備份與維護模式開始。
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-files
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-maintenance-mode on編輯 ERPNEXT_VERSION(位於 ~/gitops/erpnext.env),然後產生設定、拉取映像並執行 migration。
docker compose --project-name erpnext \
--env-file ~/gitops/erpnext.env \
-f compose.yaml \
-f overrides/compose.mariadb.yaml \
-f overrides/compose.redis.yaml \
-f overrides/compose.https.yaml \
config > ~/gitops/erpnext.yaml
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml pull
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml up -d
docker compose --project-name erpnext exec backend \
bench --site erp.example.com migrate
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-maintenance-mode off維護模式很重要,因為 migrate 執行時會修改 schema。若使用者在資料表完成 migration 前提交文件,就可能需要手動修復資料。
每次只升級一個 major version,並在每個步驟之間建立備份。某個版本的 migration 程式是針對從前一個版本升級而撰寫的,因此跳過 major version 會以沒有人測試過的組合執行 migration。
該儲存庫也提供 overrides/compose.migrator.yaml,會加入一個每次啟動都執行 bench --site all migrate 的容器。這很方便,但也表示只要變更標籤,docker compose up 就可能在無人監控的情況下對 production database 執行 migration。對商務系統而言,請把執行 migrate 視為當天明確做出的決策。
強化儲存客戶紀錄的伺服器
首次登入時變更 Administrator 密碼。評估用 compose 檔案將 admin 設為該密碼,這種習慣會被帶入正式環境。
將 DB_PASSWORD 改成不同於 example.env 中 123 的值。該值會以純文字寫入產生的 ~/gitops/erpnext.yaml,因此請 chmod 600 該檔案,並避免將它放入任何 git repository。若要使用更強的作法,overrides/compose.mariadb-secrets.yaml 會從 Docker secret 檔案讀取密碼,而不是從環境變數讀取。在 Docker Compose 中處理 env 檔案與 secrets說明其中的取捨。
只發布必要的連接埠。使用 HTTPS override 時,只有 80 和 443 會對外暴露。不要在 db service 上新增 ports mapping,讓資料庫用戶端更容易連線;這會把 MariaDB 暴露在公開網際網路上。請改用 docker compose --project-name erpnext exec backend bench mariadb。在主機上允許 22、80 和 443,拒絕其餘連線,並同時檢查供應商的獨立網路防火牆。
對所有持有 System Manager role 的帳戶,在 System Settings 中啟用 two factor authentication。該 role 可以讀取所有文件並匯出所有資料表,因此應將它視為 administrator account,而不是為了方便而授予的權限。如果執行多個自架應用程式,使用 Authentik 作為自架 single sign-on provider,會比每個應用程式再設定一組密碼更適合。
修補主機,並在 kernel 更新後重新開機。在確認 stack 能夠恢復前,先檢查產生的檔案中每個 service 是否設定 restart policy;沒有該設定的 stack 會在重新開機後維持停止狀態。讓 Docker Compose stack 在重新開機後再次啟動說明 systemd 部分的設定。
ERPNext 不再適合單一 VPS 時
一台 VPS 可以長期支撐小型公司。以下跡象表示它已經不敷使用:
- 背景工作持續堆積,導致電子郵件與匯入作業延遲數分鐘甚至數小時。
docker inspect回報容器出現"OOMKilled": true或結束代碼 137。- 原本需要 2 秒的報表現在需要 30 秒,而且 MariaDB 是占用 CPU 的程序。
- 備份執行時間過長,導致一次排程執行與下一次重疊。
先為 MariaDB 提供不與其他服務共用的資源,因為資料庫與 Python worker 會競爭相同的記憶體,而 buffer pool 對記憶體的需求通常更高。升級應用程式伺服器的效果通常不如預期。在 Docker 或主機上執行資料庫說明這項選擇,而在調整期間,在 Docker Compose 中設定記憶體限制可避免單一容器耗盡其他容器的資源。
接著增加 queue worker,而不是擴充 web capacity。ERPNext 的慢速工作都在背景執行,例如報表產生與大量匯入。增加 worker 容器的成本低於升級更大的伺服器,而且能直接解決使用者實際抱怨的問題。
FAQ
ERPNext 在 VPS 上需要多少 RAM?
官方發布的指引從 4 GB 搭配 2 vCPU 開始,但這個層級僅供評估使用。若公司每天使用,請規劃 8 GB RAM、4 vCPU,以及 100 GB SSD。低於此規模時,kernel 的 out-of-memory killer 會在負載過高時停止容器,而 docker inspect 會將此事件報告為 "OOMKilled": true,結束代碼為 137。這些數值只是起始點,不是實測結果,因此請在第一個月持續監控實際記憶體使用量。
可以在 production 中執行 pwd.yml 嗎?
不可以。專案的 README 說明,pwd.yml 僅供短期評估使用,且無法在其中安裝自訂 app。請使用 compose.yaml 搭配 MariaDB、Redis 和 HTTPS 覆寫設定,再以 docker compose config 將這些設定產生為單一檔案,然後執行該檔案。
為什麼 ERPNext site 建立後立即無法連線?
前端預設會依 HTTP Host 標頭選擇要提供的 site,因此 site 名稱必須與瀏覽器中的網域相符。若 site 建立時使用 erpnext 作為名稱,就不會在 erp.example.com 提供服務。請以網域作為 site 名稱建立 site,或在 env 檔案中將 FRAPPE_SITE_NAME_HEADER 設為該 site 名稱,重新產生 compose 檔案,再重新啟動 stack。
ERPNext 備份必須包含哪些內容?
必須將以下 4 個檔案放在一起:-database.sql.gz dump、-files.tar 與 -private-files.tar archive,以及 -site_config_backup.json 設定副本。執行 bench --site erp.example.com backup --with-files 會產生這 4 個檔案。設定副本包含 encryption_key,因此還原時若缺少該檔案,已儲存的整合密碼將無法解密,並顯示為 Encryption key is invalid! Please check site_config.json。
如何升級 ERPNext 才不會損壞資料?
先使用 --with-files 備份,啟用 maintenance mode,在 env 檔案中修改 ERPNEXT_VERSION,重新產生 compose 檔案,執行 pull,啟動 stack,接著執行 bench --site erp.example.com migrate,最後關閉 maintenance mode。每次只升級 1 個 major version,並先閱讀 release notes,因為 migrate 會改寫 schema 和文件資料,而且無法復原。若要 rollback,必須還原開始時建立的備份。