VPSにDockerでERPNextをセルフホストする方法
ERPNextをVPSで運用する手順です。11個のコンテナ構成、必要な容量、TLS、送信メール、バージョン固定、実際に復元を確認したバックアップまで解説します。
実行のために準備する内容
VPS で ERPNext をセルフホストする作業は、1 つのコマンドで完了するインストールではなく、運用作業です。公式の Docker Compose スタックは 11 個のコンテナで構成され、総勘定元帳と顧客情報を保持します。そのため、以下のすべてで高い運用水準が求められます。バックアップは復元できることを確認して初めてバックアップになります。また、イメージタグを固定しないと、予期しないスキーマ移行が発生するおそれがあります。
ここでは、いくつかの名称を使います。ERPNext は業務アプリケーションです。Frappe はその基盤となる Python フレームワークです。Bench はサイトを管理するコマンドラインツールで、コンテナ内にあらかじめインストールされています。サイト は 1 つのテナントです。1 つの MariaDB データベースと、アップロードしたファイルを格納する 1 つのディレクトリで構成されます。ここで示すほぼすべてのコマンドは、backend コンテナ内で bench を実行し、名前を指定した 1 つのサイトを対象にします。
このガイドでは、プロジェクトが保守しているデプロイメントである frappe_docker リポジトリを使用します。以下のすべてのコマンドは、2026 年 8 月にこのリポジトリで確認済みです。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
}
]公開されている目安では、1 人のユーザーもログインしていない段階で 2 vCPU と 4 GB の RAM が必要です。これは評価用の構成です。いずれも開始点であり、このガイドで測定した値ではありません。実際に必要な容量は、扱うドキュメントの量によって決まります。最後の行は、公開された最低要件ではありません。おおむね、メモリを意識しなくてよくなる水準です。
小容量のプランについては、現実的に判断してください。1 GB または 2 GB の VPS ではスタックを起動できますが、最初のインポートや時間のかかるレポート処理で停止します。長時間稼働する 9 個のコンテナ、MariaDB のバッファプール、レポートを生成する Python worker をそのメモリ容量に収められないためです。停止は正常には処理されません。kernel の out of memory killer がコンテナを停止し、そのコンテナで docker inspect を実行すると、終了コード 137 の "OOMKilled": true が表示されます。worker がジョブの途中で停止すると、送信済みのドキュメントでもバックグラウンド処理が途中までしか完了しません。
毎日 ERPNext を使用する会社にとって、8 GB、4 vCPU、100 GB の SSD が現実的な最低ラインです。最初に不足するのは RAM です。ディスク使用量は予想以上の速さで増加します。すべての添付ファイルとローカルバックアップが、データベースと同じボリュームに保存されるためです。
11 個のコンテナと、それぞれの役割
スタックが起動し、9 個のコンテナが実行中になったら docker compose ps を実行します。残りの 2 個である configurator と create-site は、1 回だけ処理を実行して終了します。これが合計 11 個になる理由です。
backendは gunicorn で Frappe アプリケーションを実行します。benchはここで動作します。frontendは nginx です。静的アセットを配信し、それ以外のリクエストをバックエンドへ転送します。queue-shortとqueue-longは RQ (Redis Queue) ワーカーです。送信メール、インポート、レポート生成などのバックグラウンドジョブを実行します。schedulerは、スケジュールされたレポートや自動反復ドキュメントなど、時間ベースのジョブを実行します。websocketは、ブラウザーのライブ更新を担う socket.io プロセスです。dbは MariaDB です。redis-cacheとredis-queueは別々の Redis インスタンスです。一方はキャッシュ用、もう一方はジョブキュー用です。
この分割は理解しておく価値があります。どのログを確認すべきか判断できるためです。メールがキューに滞留している場合はキューワーカーの問題なので、docker compose logs -f queue-short が適切なコマンドです。ページは読み込まれるものの通知バッジが更新されない場合は、websocket の問題です。どちらの場合も backend のログを読むのは、時間の無駄です。
本番用の compose ファイルでインストールする
リポジトリには 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 でイメージタグを固定します。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 ファイルを 1 つ生成して起動します。
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 は何も起動しません。ベースファイルとオーバーライドをマージし、すべての変数を置換した結果を出力します。その後、その生成済みファイルを実行します。この手順には利点があります。実行中のスタックを 1 つのファイルとして確認してコミットできるため、誰かが env ファイルを編集した場合やリポジトリを pull した場合でも、実行中の構成が意図せず変わりません。複数の 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 は 1 つも表示されません。
ここでは、2 つの問題がよく発生します。Docker では --mariadb-user-host-login-scope=% は省略できません。app コンテナは Docker ネットワーク経由で MariaDB に接続するため、MariaDB からはリモートホストとして認識されます。そのため、localhost に限定されたデータベースユーザーではログインできません。site の作成は、root ユーザーを示す MariaDB の access denied エラーで失敗します。% のスコープを指定すると、その新しい site のユーザーはプライベートネットワーク上の任意のホストからアクセスできるようになります。
もう 1 つは site 名です。デフォルトでは、frontend は HTTP の Host ヘッダーから提供する site を選択します。そのため、erpnext として作成した site は、両方が存在していても erp.example.com では到達できません。上記のようにドメインに合わせて site 名を付けるか、env ファイルの FRAPPE_SITE_NAME_HEADER に site 名を設定し、compose ファイルをもう一度生成してください。
HTTPS と、動作前に満たす必要がある条件
compose.https.yaml の設定では、Traefik がポート 443 で動作し、ポート 80 からのアクセスを HTTPS にリダイレクトし、Let's Encrypt に証明書を要求します。TLS(トランスポート層セキュリティ)により、請求書やセッション Cookie が平文のままネットワーク上を流れることを防ぎます。
証明書を発行するには、2 つの条件を満たす必要があります。満たさなければ、証明書は発行されません。erp.example.com の DNS A レコードが、あらかじめ VPS を指していなければなりません。また、インターネットからポート 80 と 443 に到達できる必要があります。Let's Encrypt は、ポート 80 の HTTP-01 チャレンジで、その名前を管理していることを確認するためです。プロバイダー側のネットワークファイアウォールと、サーバー上のファイアウォールの両方を確認してください。これらは別々の制御であり、パネル側のファイアウォールは見落とされやすい箇所です。
証明書は cert-data ボリューム内の /letsencrypt/acme.json に保存されます。ブラウザーに自分の証明書ではなくデフォルトの証明書が表示される場合は、docker compose --project-name erpnext ps でプロキシサービス名を確認し、そのログから ACME(自動証明書管理環境)のエラーを調べてください。同じサーバーで他の Web アプリケーションも運用しますか。複数の Docker Compose アプリの前段に 1 つの Traefik インスタンスを置く方法では、ポート 443 の競合を避けながらプロキシを共有する方法を説明しています。このようなサーバー上の 2 つ目のアプリは、顧客向けであることがよくあります。自己ホスト型 Chatwoot サポートデスクも同じプロキシの背後で動作するため、請求書を扱う担当者が同じ場所で顧客のメールやチャットにも対応できます。
送信メール。請求書がサーバーから出ていない
これは多くの ERPNext ガイドが省略する手順ですが、システムが実用になるかどうかを左右します。送信メールが機能しなければ、請求書は顧客に届かず、パスワードリセットも届かず、スケジュール済みレポートも配信されません。この構成にはメールサーバーが含まれていません。
VPS から port 25 を使って直接メールを送信しないでください。多くのプロバイダーは、新規アカウントの outbound port 25 をブロックしています。送信できたとしても、新しい VPS のアドレスには送信実績がないため、拒否されたり spam フォルダーに分類されたりします。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" ではなく数値として保存します。ファイルを読み戻し、この 2 つの値が引用符で囲まれていないことを確認します。
docker compose --project-name erpnext exec backend \
cat sites/erp.example.com/site_config.jsonmail_password はコマンドラインではなく Email Account 画面で設定してください。暗号化して保存され、shell history に残らなくなります。
次に、実際のメッセージを送信します。Sales Invoice を作成し、自分が管理するアドレスにメールで送信します。その間、次のコマンドでキューを監視します。
docker compose --project-name erpnext logs -f queue-short送信メールはバックグラウンドジョブです。そのため、メッセージが届かない場合は、ブラウザーのエラーではなく、そのログに failed job として表示されることが通常です。送信ドメインには SPF(sender policy framework)と DKIM(domainkeys identified mail)のレコードも公開し、DMARC ポリシーを追加してください。これらがないと、技術的に正しい請求書でも顧客の spam フォルダーに入ります。経路全体を自分で管理する場合は、self-hosted Mailcow mail server を使用すると、ERP とは別のサーバー上で管理できる relay を構築できます。
実際に復元できるバックアップ
データベースダンプだけでは、ERPNext のバックアップにはなりません。添付ファイルとプライベートファイルは MariaDB ではなく sites ディレクトリに保存されます。データベースだけを復元すると、アップロード済みのすべての発注書がリンク切れになります。
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-filessites ボリューム内の sites/erp.example.com/private/backups に、次の4つのファイルが作成されます。
-database.sql.gzダンプ- 公開ファイルの
-files.tarアーカイブ - プライベートファイルの
-private-files.tarアーカイブ - サイト設定の
-site_config_backup.jsonコピー
4番目のファイルは削除されがちですが、最も重要です。このファイルには encryption_key が含まれています。これは、Frappe が保存済みパスワードの暗号化に使う鍵です。メールアカウントの認証情報、決済ゲートウェイの鍵、すべての連携用 Secret が対象です。一致する鍵を使わずにデータベースを復元すると、サイトは正常に読み込まれますが、メール送信は次のエラーで失敗します。
frappe.exceptions.ValidationError: Encryption key is invalid! Please check site_config.json4つのファイルは、必ず一緒に保管してください。
次に、サーバーの外へコピーします。ボリューム内のバックアップはサーバー障害に耐えられません。また、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 によってリポジトリを引き続き読み取れることを確認できるためです。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.comERPNext ではバージョン固定がより重要な理由
静的サイトでは、イメージタグを固定しないと予期しない再起動が発生します。ERPNext では、これがスキーマ移行を意味します。bench migrate はデータベーステーブルを書き換え、ドキュメントデータを書き換えることもあります。元に戻す機能はありません。ロールバックはバックアップからのリストアであり、docker compose down ではありません。
そのため、タグを固定します。ERPNEXT_VERSION=v16.32.1 は、2026 年 8 月にリポジトリの pwd.yml 自体で固定されていたリリースです。この番号を確認せずにそのまま使い続けないでください。現在のリリースは frappe/erpnext リリースページ に、利用可能なイメージタグは 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~/gitops/erpnext.env の ERPNEXT_VERSION を編集し、その後にレンダリング、pull、migrate を実行します。
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 の実行中にスキーマが変更されるためです。移行途中のテーブルに対してユーザーがドキュメントを送信すると、レコードを手作業で修復する事態につながります。
メジャーバージョンは一度に1つずつ更新し、各ステップの間にバックアップを取得してください。各リリースの移行コードは、直前のリリースからのアップグレードを前提に作成されています。そのため、メジャーバージョンを飛ばすと、誰もテストしていない組み合わせで移行が実行されます。
このリポジトリには overrides/compose.migrator.yaml も含まれており、起動するたびに bench --site all migrate を実行するコンテナが追加されます。便利な機能です。一方で、タグを変更した docker compose up によって、監視する人が誰もいないまま本番データベースが移行される可能性があります。業務システムでは、migrate はその朝に判断した作業として実行してください。
顧客記録を保持するサーバーのハードニング
初回ログイン時に Administrator のパスワードを変更します。評価用の compose ファイルでは、admin がそのパスワードとして設定されており、この習慣が本番環境にも持ち込まれます。
example.env 内の 123 から DB_PASSWORD を変更します。この値はレンダリング後の ~/gitops/erpnext.yaml に平文で含まれるため、chmod 600 でファイルを保護し、git リポジトリには置かないでください。より強固な方法として、overrides/compose.mariadb-secrets.yaml は環境変数ではなく Docker secret ファイルからパスワードを読み取ります。Docker Compose での環境変数ファイルと Secret の扱いでは、これらのトレードオフを説明しています。
必要なポートだけを公開します。HTTPS 用の override では、80 と 443 だけが公開されます。データベースクライアントから接続しやすくするために、db service に ports のマッピングを追加しないでください。これにより MariaDB がパブリックインターネットに公開されます。代わりに docker compose --project-name erpnext exec backend bench mariadb を使用します。ホストでは 22、80、443 を許可し、それ以外を拒否します。プロバイダー側の別個のネットワークファイアウォールも確認してください。
System Manager ロールを持つすべてのアカウントで、System Settings の二要素認証を有効にします。このロールではすべてのドキュメントを読み取り、すべてのテーブルをエクスポートできるため、単なる利便性のためのロールではなく、管理者アカウントとして扱ってください。複数のセルフホストアプリを運用する場合は、アプリごとにパスワードを増やすよりも、セルフホスト型のシングルサインオンプロバイダーとしての Authentikを使用する方が適しています。
ホストにパッチを適用し、カーネル更新のために再起動します。スタックが復旧すると判断する前に、各 service のレンダリング後のファイルで restart ポリシーを確認してください。このポリシーがないスタックは、再起動後に停止したままになります。再起動後に Docker Compose スタックを再度起動する方法では、systemd 側の設定を説明しています。
ERPNext が 1 台の VPS では扱いにくくなるとき
1 台の VPS で小規模な会社の業務を長期間運用できます。次の兆候が出たら、容量が不足しています。
- バックグラウンドジョブが滞留し、メールやインポートの反映が数分から数時間遅れます。
docker inspectが、"OOMKilled": trueまたは終了コード 137 のコンテナを報告します。- 2 秒で完了していたレポートに 30 秒かかり、MariaDB が CPU を占有するプロセスになります。
- バックアップの実行時間が長くなり、次回のスケジュール実行と重なります。
まず、MariaDB に専用のリソースを割り当てます。データベースと Python ワーカーが同じメモリを奪い合い、より多くのメモリを必要とするのはバッファプールだからです。アプリケーションサーバーを大きくしても、期待するほど改善しません。データベースを Docker またはホスト上で実行する ではこの判断を扱い、Docker Compose でメモリ制限を設定する では、作業中に 1 つのコンテナが他のコンテナのリソースを使い果たすことを防ぎます。
次に、Web 容量ではなくキューワーカーを追加します。ERPNext で時間がかかる処理は、レポート生成や一括インポートなどのバックグラウンド処理です。ワーカーコンテナを増やすほうが大きなサーバーを導入するより安価で、ユーザーが実際に困っている遅延を解消できます。
FAQ
ERPNext を VPS で実行するには、どのくらいの RAM が必要ですか?
公開されている指針では、4 GB の RAM と 2 vCPU から始める構成が示されています。ただし、この構成は評価用途に限られます。日常的に利用する企業では、8 GB の RAM、4 vCPU、100 GB の SSD を計画してください。それ未満では、負荷時に kernel の out of memory killer がコンテナを停止します。この状態は docker inspect により終了コード 137 の "OOMKilled": true として報告されます。これらは計測値ではなく開始時点の目安です。そのため、最初の 1 か月は実際のメモリ使用量を監視してください。
本番環境で pwd.yml を実行できますか?
いいえ。プロジェクトの README では、短期間の評価用途のみを想定していると説明されています。また、カスタムアプリをインストールできないことも記載されています。MariaDB、Redis、HTTPS のオーバーライドを含む compose.yaml を使用し、docker compose config で 1 つのファイルにレンダリングして、そのファイルを実行してください。
作成直後に ERPNext サイトへ接続できないのはなぜですか?
フロントエンドはデフォルトで、HTTP の Host ヘッダーに基づいて提供するサイトを選択します。そのため、サイト名はブラウザーで使用するドメインと一致している必要があります。erpnext として作成したサイトは、erp.example.com では提供されません。ドメインをサイト名としてサイトを作成するか、env ファイルの FRAPPE_SITE_NAME_HEADER をサイト名に設定してください。その後、compose ファイルを再度レンダリングして、stack を再起動します。
ERPNext のバックアップには何を含める必要がありますか?
4 つのファイルを一緒に保管します。-database.sql.gz の dump、-files.tar と -private-files.tar の archive、-site_config_backup.json の config コピーです。bench --site erp.example.com backup --with-files を実行すると、4 つすべてが生成されます。config コピーには 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 つだけ進め、最初に release notes を確認してください。migrate は schema と document data を元に戻せない形で書き換えるためです。ロールバックする場合は、開始時点で作成したバックアップを復元します。