DockerでVPSにSupabaseをセルフホストする方法
公式のSupabase DockerスタックをVPSで動かす手順です。必ず置き換えるSecret、約14サービスの役割、必要なRAM容量、バックアップ、更新方法を解説します。
構築するもの
Supabase のセルフホスティングでは、自分のサーバー上で公式の Docker Compose スタックを実行します。Postgres、その前段の REST API、認証サービス、ファイルストレージ、リアルタイム WebSocket、Studio ダッシュボードが含まれます。1 つのリポジトリを clone し、1 つの .env ファイルを編集して、約 14 個のコンテナを起動します。これらが連携して、自分で管理する Supabase プロジェクトとして動作します。
インストール自体は短時間で完了します。問題が起きるのは .env ファイルです。このファイルにはリポジトリで公開されているデモ用 Secret が含まれています。そのデフォルト値のまま起動したスタックは、見つけた人なら誰でも利用できます。このガイドでは、必ず置き換える Secret、各サービスの役割、スタックに実際に必要なメモリ容量、データベースを削除せずに更新する方法を説明します。
Compose が初めての場合は、先に VPS での Docker Compose の基本を読んでください。以下では、docker compose version で既にバージョンが表示されることを前提にします。
スタックに実際に含まれるもの
Supabase は 1 つのプログラムではありません。Compose ファイルは、1 つのネットワーク上で複数の独立したサービスを起動します。それぞれの役割を把握すると、コンテナ名の一覧から問題を切り分けられるようになります。
dbは Supabase の拡張機能を読み込んだ PostgreSQL です。他のすべてのサービスがこのサービスと通信します。このコンテナが正常でない場合、他のサービスもすべて失敗します。kongは API ゲートウェイです。port 8000 で待ち受け、/rest/v1/、/auth/v1/、/storage/v1/を適切なバックエンドへルーティングします。外部公開してよいコンテナはこれだけです。restは PostgREST です。Postgres のスキーマを読み取り、REST API として提供します。そのため、新しいテーブルを追加すると、コードを書かなくても新しい endpoint が作成されます。authは GoTrue です。ユーザーを識別する JSON web token (JWT) を発行します。storageとimgproxyは、ファイルのアップロードと画像のリサイズを処理します。realtimeは、websocket 経由でデータベースの変更をストリーミングします。studioとmetaは、dashboard と、その背後で動作する admin API です。analytics(Logflare) とvectorはログを収集し、supavisorは Postgres の接続プールです。
この一覧が、以下のリソース値になる理由です。実行しているのはデータベースだけではありません。データベースに加えて、12 個のサポートサービスも実行しています。
メモリは 8 GB を前提に計画する
2026 年 7 月時点では、初期インストール直後のスタックは、独自のデータやネットワークトラフィックを含めず、常駐メモリを約 2.5 ~ 3 GB 使用します。単体で最も大きなメモリを消費するのは、分析サービスと Studio の Node.js プロセスです。2 GB のサーバーではコンテナが起動した後、通常は analytics または db のいずれかが kernel の out of memory killer によって終了します。症状としては、コンテナが exit code 137 で再起動を繰り返します。
運用で依存する環境には、8 GB の RAM と 4 vCPU を割り当ててください。4 GB でも、個人用の開発インスタンスであれば動作します。ただし、重いクエリと Studio セッションを同時に実行すると遅くなる点を受け入れる必要があります。ディスク容量も重要です。Postgres、storage volume、ログデータはすべて project directory 配下に保存されるためです。まず 40 GB から始め、使用量を監視してください。プランを選ぶ前にサービス数を確認する習慣は、self-host する対象全般で役立ちます。PhotoPrism と Immich は、quick start ページから想定される容量を大幅に上回る、実際の RAM 下限を必要とするためです。
インストール: 公式リポジトリをクローンする
サポートされている方法では、メインリポジトリから docker ディレクトリを自分で用意したプロジェクトディレクトリへコピーします。この分離が重要なのは、後で git pull を実行しても .env が上書きされないためです。
git clone --depth 1 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/* supabase-project
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
docker compose pulldocker compose pull は数 GB のイメージをダウンロードします。すべてのサービスが Pulled と表示されて終了することを確認してください。ここで manifest unknown エラーが発生する場合、固定されたイメージタグが上流で削除されています。タグを手作業で編集せず、リポジトリの新しいコピーを取得してください。
初回起動前に変更が必要な Secret
スタックを起動する前に実施してください。起動後ではありません。これらの値の一部は初回起動時にデータへ書き込まれるため、後から変更する場合はデータベースのリセットが必要になります。
リポジトリには、すべての値を正しく生成するジェネレーターが含まれています。新しい JWT Secret で署名する必要がある 2 つの API key も生成されます。
sh utils/generate-keys.sh --update-envこのスクリプトは、JWT_SECRET、ANON_KEY、SERVICE_ROLE_KEY、SECRET_KEY_BASE、REALTIME_DB_ENC_KEY、VAULT_ENC_KEY、PG_META_CRYPTO_KEY および Logflare token の新しい値を .env に書き込みます。openssl が必要ですが、通常の Ubuntu image には標準で含まれています。
このスクリプトが設定しない値が 2 つあります。.env で手動編集してください。
POSTGRES_PASSWORD。英字と数字だけを使用してください。ここに句読点を入れると、複数のサービスが文字列を連結して構築する connection string が壊れます。エラーは parsing error ではなく authentication error のように見えるため、誤った箇所を調べることになります。DASHBOARD_USERNAMEとDASHBOARD_PASSWORD。Studio の Basic 認証に使用する資格情報です。初期 password は文字どおりthis_password_is_insecure_and_should_be_updatedです。
ANON_KEY と SERVICE_ROLE_KEY を独自に作成できない理由を理解してください。どちらも JWT_SECRET で署名された JWT です。gateway はすべてのリクエストで署名を検証するため、Secret と一致しない key は {"message":"Invalid authentication credentials"} で拒否されます。これは self-hosting で最も多い失敗です。運用者が JWT_SECRET を変更したにもかかわらず、デモ用の key を使い続けています。必ず 3 つすべてを同時に生成してください。
SERVICE_ROLE_KEY は root password と同じように扱ってください。これは row level security を完全に迂回します。server side code にだけ配置し、それ以外には置かないでください。
SITE_URL と API_EXTERNAL_URL には、ユーザーが実際にアクセスする address を設定します。たとえば https://supabase.example.com です。Auth はこれらの値から email confirmation link と OAuth callback link を生成します。http://localhost:8000 のままにすると、すべてのユーザーが自分自身の machine にリダイレクトされます。
最後に、設定値を確認します。
sh run.sh secrets起動して正常性を確認する
sh run.sh start
docker compose psrun.sh start は docker compose up -d --wait を待機するため、ヘルスチェックに合格するまで戻りません。すべてのサービスが running (healthy) または running と表示されることを確認します。初回起動には 2〜4 分かかります。Postgres が初期化スクリプトを実行してからでないと、ほかのサービスは接続できないためです。
コンテナが再起動を繰り返している場合は、サービス名を指定してログを確認します。
docker compose logs db
docker compose logs authStudio はポート 8000 で利用できます。設定したダッシュボードのユーザー名とパスワードの入力を求められます。
パブリックインターネットに port 8000 を公開しない
Kong の port 8000 は平文の HTTP を使用します。すべての API key とユーザーの password がネットワーク上を平文で通過します。Studio の認証情報も、暗号化ではなく base64 エンコードを使用する Basic 認証です。
Kong の前段に reverse proxy を配置し、そこで TLS(transport layer security)を終端します。Kong は loopback address に bind し、他の接続元から到達できないようにします。docker-compose.yml では、kong の port mapping を 127.0.0.1:8000:8000 に変更し、proxy からそこへ転送します。証明書については 複数の Compose アプリの前段に Traefik を置く方法で説明しています。同じ proxy が、この stack から 90 年代のビデオ店として再構築した Jellyfin libraryのような用途まで、サーバー上の他のサービスもすべて前段で受ける構成になります。各サービスには、別の port を開くのではなく hostname が必要です。dashboard を自分だけが使う場合は、proxy を省略し、SSH tunnel 経由で loopback port に接続します。これは、self-hosted open-krittが scanning interface をパブリックインターネットから完全に隠すために使用している方法と同じです。
残りの port も firewall で閉じてください。Docker は独自の iptables rule を作成して port を公開するため、単純な ufw の設定ではその通信を検知できません。この落とし穴については、Docker container が ufw rule を無視する理由で説明しています。
データベースをバックアップし、ディレクトリはバックアップしない
Postgres のデータは ./volumes/db/data の bind mount に保存されています。コンテナの実行中にこのディレクトリをコピーすると、不完全なコピーになります。Postgres は書き込みをバッファーし、ディスク上のファイルが整合するのは checkpoint の時点だけだからです。これを復元すると通常は動作しますが、最後のトランザクションが気付かないうちに失われることがあります。これはバックアップで起きる障害として最悪です。
代わりに dump を作成します。pg_dumpall はコンテナ内で実行され、整合性のあるスナップショットを作成します。
docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sql信頼する前に、ファイルが空でないことを確認してください。その後、スケジュールに従って dump をサーバー外へ転送します。これは restic による暗号化されたオフサイトバックアップで行います。スケジュールされた dump が何も通知せずに失敗すると、バックアップが存在しない場合と区別できません。そのため、cron または systemd のジョブが非ゼロで終了したときに スマートフォンへアラートを送信するよう設定してください。同時に .env もバックアップします。JWT_SECRET を失うと、発行済みのすべての token が無効になり、保存されている暗号化済みの Secret を読み取れなくなります。
アップロードされたファイルは ./volumes/storage に保存されます。これらは通常のファイルなので、単純なコピーで問題ありません。
データを失わずに更新する
Supabase は docker-compose.yml でイメージのバージョンを固定しているため、アップグレードするまで内容は変わりません。手作業で構築するスタックでも、バージョン固定の方式を採用する価値があります。そのため、セルフホストの RustDesk リレーでは、変動するタグを追従せず、2 つのサーバーイメージを固定しています。アップグレードは、時間に余裕のある日に自分で実施する作業にすべきです。毎回、最初にダンプを取得してください。
docker compose pull
sh run.sh recreaterecreate はスタックを停止し、新しいイメージで再起動します。データはコンテナ内ではなくホスト上の bind mount に保存されるため、保持されます。メジャーバージョンを更新する前に、リポジトリ内の CHANGELOG.md を確認してください。Postgres のメジャーアップグレードは自動で実行されず、ダンプとリストアが必要です。
Compose ファイル自体の変更を反映するには、upstream リポジトリを再度 clone し、その docker ディレクトリをプロジェクトへコピーします。このとき、.env を上書きしないように注意してください。
データベースを含むすべてを削除する完全リセットは別のスクリプトで実行し、確認を求められます。
sh reset.shFAQ
API 呼び出しで「Invalid authentication credentials」が返るのはなぜですか?
ANON_KEY または SERVICE_ROLE_KEY が、現在 .env に設定されている JWT_SECRET で署名されていません。ゲートウェイはすべてのリクエストで署名を検証するため、不一致があると拒否します。sh utils/generate-keys.sh --update-env ですべてを同時に再生成し、その後 sh run.sh recreate を実行して、各サービスに新しい値を読み込ませてください。
2 GB の VPS でセルフホスト型 Supabase を実行できますか?
安定して実行することはできません。2026 年 7 月時点で、このスタックは約 14 個のサービスを実行するため、アイドル時でも約 3 GB を使用します。そのため 2 GB のサーバーでは out of memory killer によってコンテナが停止し、docker compose ps に exit code 137 が表示されます。本番環境には 8 GB を使用し、個人開発でも 4 GB を最低容量としてください。
セルフホスト型 Supabase には edge functions が含まれますか?
はい。Compose ファイルには Deno ベースの functions runtime が含まれており、./volumes/functions の下に配置した内容を提供します。ただし、ホスト型プラットフォームのグローバルなデプロイネットワークは含まれません。そのため、functions は 1 台のサーバー上の 1 か所で実行されます。
Postgres データベースに直接接続するにはどうすればよいですか?
サーバー自体で対話型シェルを使用する場合は docker exec -it supabase-db psql -U postgres を使用します。外部クライアントから接続する場合は、port 5432 で Supavisor 経由に接続し、user postgres.<POOLER_TENANT_ID> と POSTGRES_PASSWORD を使用します。この port をインターネットに公開しないでください。VPN または SSH tunnel 経由で接続してください。
auth の確認メールから localhost にリンクされるのはなぜですか?
.env の SITE_URL と API_EXTERNAL_URL がデフォルト値のままになっています。auth service は、これら 2 つの値を基にすべての確認リンクとパスワードリセットリンクを生成します。そのため、指定されたアドレスを送信します。両方を実際の公開 URL に設定し、stack を再作成してください。