SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-27

openGymをVPSで自分でホストする方法

Docker ComposeでopenGymをVPSへデプロイする手順です。v1.2.7の固定、初回Passkey前のTLS、JSONデータの保存先、読み取り専用MCPサーバーの配置を確認できます。

自分で openGym をホストすると得られるもの

openGym は、リポジトリを clone し、.env の2行を編集して、TLS(transport layer security)を終端するリバースプロキシの背後で docker compose up -d --build を実行すると、自分でホストできます。openGym は、週間プラン、ガイド付きワークアウト、セットごとの記録、体重の推移を管理するジム・体重トラッカーです。ライセンスは AGPL-3.0 で、すべてのデータをディスク上のプレーンな JSON ファイルに保存するため、データベースサーバーを実行する必要はありません。

構成は、長時間実行する2つのコンテナ、React のビルドを配信する nginx コンテナと API を保持する Node コンテナに加え、初回起動時に約 140 MB のエクササイズ画像と GIF をダウンロードする one-shot ジョブで成り立ちます。

公開サーバーにデプロイする場合、プロジェクトの README が示唆しているものの明記していない点が2つあります。Passkey ログインはホスト名に紐付くため、最初のログインより前にドメインとその証明書を用意する必要があります。後から用意することはできません。また、オプションの MCP サーバーは読み取り専用で、スタック内ではなく AI クライアントが実行されているマシン上で動作します。そのため、データが VPS 上にある場合に必要な作業が変わります。

openGym はまだ新しいソフトウェアです。最初のタグ付きリリース v1.0.0 は 20 July 2026 付けで、v1.2.7 は 18 August 2026 にリリースされました。約1か月で13個のタグが付いているため、アプリは現在も活発に更新されています。そのため、デフォルトブランチのその時点の内容でビルドするのではなく、リリースタグを checkout してください。

初回ログイン前にドメインを決める

openGym へのサインインには Passkey を使用します。Passkey は relying party ID(RP ID)に紐付きます。RP ID は認証情報を作成したドメインです。ブラウザーが Passkey を作成できるのは HTTPS 接続上だけです。唯一の例外は localhost です。

この制約は、スマートフォンで操作するときに問題になります。別のデバイスから http://203.0.113.10:8080 を開いても、Passkey のプロンプトはまったく表示されません。ブラウザーが、通常の HTTP origin または単独の IP アドレス上で認証情報を作成しないためです。プロジェクトのトラブルシューティングにも同じ説明があります。プロンプトが表示されない場合は、http:// または IP アドレスを使用しています。

さらに、RP ID はユーザーがすでに登録したすべての認証情報に組み込まれています。後から RP_ID を変更すると、ユーザーのデバイスに保存された Passkey と一致しなくなり、誰もサインインできません。最初にホスト名を決め、DNS を VPS に向けてください。全員が Create profile をタップする前に、証明書も動作させておきます。

Docker Compose で openGym をデプロイする

Compose ファイルは ./data./media を自身の場所を基準に bind mount するため、clone 先のディレクトリ自体がデータベースになります。永続化できる場所に配置してください。

sudo install -d -o "$USER" -g "$USER" /opt/opengym
git clone https://gitea.com/DuarteSantos/openGym /opt/opengym
cd /opt/opengym
cp .env.example .env

README には現在も github.com の clone URL が記載されています。このアドレスはすでに解決できません。上記の Gitea リポジトリが現在のプロジェクトの正式な場所です。

.env を編集します。VPS では3行が重要です。

RP_ID=gym.example.com
ORIGIN=https://gym.example.com
WEB_PORT=127.0.0.1:8080

RP_ID はホスト名だけの値で、ORIGIN はスキームを含む完全な URL です。アドレスバーの表示と完全に一致させてください。一致しないと、verification failed でログインに失敗します。WEB_PORT の値については、ポート 8080 を非公開にするセクションで説明します。

docker compose up -d --build
docker compose ps
docker compose logs media

docker compose ps では、webapi が running で、media が終了コード 0 で exited になっていることを確認できます。この終了は正常です。media ジョブは処理が1回限りのダウンロードであるため、restart: "no" しています。ログの末尾には ✓ Exercise media ready で始まる行があり、ls media/img | wc -l は 0 ではなく数百程度を出力するはずです。ディレクトリが空の場合はダウンロードに失敗しています。その場合、アプリは画像が空の exercise カードを表示します。

ここで --build フラグは省略できません。Compose ファイルは ghcr.io にある、現在は公開されていないビルド済みイメージを指定しています。そのため docker compose pulldenied または manifest unknown で失敗し、代わりに2つのサービスが直前に clone したソースからビルドされます。どちらにも、この目的のための build セクションがあります。Compose 自体に慣れていない場合は、まず VPS 上の Docker Compose から始めてください。

このプロジェクトはまだ新しいため、バージョンを固定する

そのレジストリ名前空間はなくなったため、固定できるイメージタグは残っていません。代わりに、ディスク上のチェックアウトを固定します。コンテナで実行されるアプリケーションのバージョンは、これによって決まります。

cd /opt/opengym
git fetch --tags
git checkout v1.2.7

git status には、そのタグで detached HEAD になったことが表示されます。サーバーではこれが望ましい状態です。別のタグをチェックアウトするまで、現在の状態は変わりません。

次に、Compose がレジストリへアクセスしないようにします。docker-compose.override.yml に次の内容を記述してください。Compose はこのファイルを自動的に読み込み、管理対象のファイルに重ねて適用します。スカラー値は上書きファイルの値に置き換わるため、git 内のファイルを編集する必要はなく、git pull もクリーンな状態に保てます。マージ規則の詳細は、Compose が上書きファイルをマージする方法を参照してください。

services:
  api:
    pull_policy: build
  web:
    pull_policy: build

これで、後続の docker compose up -d は pull に失敗せず、手元にあるソースからビルドされます。マージが適用されたことを確認してから、そのタグで再ビルドします。

docker compose config | grep pull_policy
docker compose up -d --build

リバースプロキシでTLSを終端する

コンテナは平文のHTTPで通信します。証明書は前段の何らかのコンポーネントで保持する必要があります。Caddyを使うのが最短です。Let's Encryptから証明書を自動的に取得し、更新するためです。

gym.example.com {
    reverse_proxy 127.0.0.1:8080
}

nginx、Traefik、Nginx Proxy Managerも同じ方法で動作します。Cloudflare Tunnelも同様です。プロジェクトのドキュメントに記載されており、インバウンドポートを開放する必要もありません。

curl -sI https://gym.example.com | head -1

警告なしでHTTP/2 200が返るはずです。次にブラウザーでサイトを開き、Create profileをタップします。パスキーのプロンプトが表示された後、ログイン時にverification failedと報告された場合は、RP_IDまたはORIGINがアドレスバーのURLと一致していません。.envを修正して、再度docker compose up -dを実行します。これによりコンテナが再作成され、新しい値が読み込まれます。docker compose restartでは.envは再読み込みされません。

パブリックインターネットからポート 8080 へのアクセスを遮断する

デフォルトでは、Web サービスがすべてのインターフェースで 8080 を公開します。そのため、同じホスト上でプロキシが HTTPS を提供していても、公開 IP アドレスの通常の HTTP 経由でアプリにアクセスできます。ファイアウォールのルールを追加しても、この問題は解決しません。Docker は nat テーブルの DNAT ルールでポートを公開し、そのトラフィックを FORWARD chain で処理します。ここでは Docker 自身のルールがアクセスを許可します。一方、ufw のルールは INPUT path にあります。したがって、sudo ufw deny 8080/tcp は何も遮断しません。

ループバックアドレスだけで待ち受けるようにポートを公開すれば解決します。compose file では "${WEB_PORT:-8080}:${NGINX_PORT:-80}" をマッピングしているため、WEB_PORT に設定した値がそのマッピングの左側に置き換えられます。Docker の short syntax では、そこに ip:port の組み合わせを指定できます。これが WEB_PORT=127.0.0.1:8080 が機能する理由です。

docker compose config
sudo ss -ltnp | grep 8080

統合後の設定では、web service の ports の下に host_ip: 127.0.0.1 が表示されていることを確認します。ss には 127.0.0.1:8080 が表示され、0.0.0.0:8080 は表示されない状態にします。別のマシンからは、curl http://<your-vps-ip>:8080 への接続が拒否されるかタイムアウトし、HTTPS のホスト名は引き続き動作するはずです。

プロファイル作成後にサインアップを停止する

サインアップはデフォルトで有効になっており、ゲストモードも有効です。公開ホスト名では、URLを見つけた人なら誰でもサーバー上にプロファイルを作成できます。まず自分のプロファイルを登録し、ユーザーIDを確認します。ls data/を実行すると、各ユーザーに対応するstate-<uid>.jsonという名前のファイルが一覧表示されます。この<uid>が必要な値です。

ADMIN_UIDS=<your-uid>
INVITE_ONLY=1
ALLOW_GUEST=0

もう一度docker compose up -dを実行します。SettingsにAdminダッシュボードが表示され、招待コードの生成と無効化ができるようになります。これにより、トレーニングを行う人だけが登録でき、それ以外の人は登録できなくなります。openGymは外部のIDプロバイダーを認識しないため、これらの招待コードが制御するのはこのアプリだけであり、サーバー上の他のサービスには影響しません。実行するすべてのサービスでユーザーごとに1つのアカウントを共有したい場合は、前段にAuthentikをforward authプロキシとして配置することで、openGym独自のpasskeyログインが読み込まれる前にホスト名へのアクセスを制御できます。

データの保存場所と、それを保護するバックアップ

すべてのデータは ./data ディレクトリにあり、API コンテナ内の /data にマウントされています。ファイルは4種類あります。db.json にはプロファイルと公開パスキー認証情報、state-<uid>.json には各ユーザーのルーティン、ワークアウト、体重、secret にはセッション Cookie の暗号鍵、vapid.json には初回起動時に生成されたプッシュ通知用の鍵が保存されています。

cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api

tar は API がファイルを書き込んでいる可能性がある状態でファイルをコピーするため、最初に API を停止します。コピー途中の JSON ファイルを復元すると、壊れた JSON ファイルになる可能性があります。停止と起動にかかる時間は約2秒です。次に、アーカイブをサーバーの外部へコピーします。VPS 上に置いたアーカイブは、その VPS が失われると残りません。media/ はバックアップ対象から除外します。これは運動画像140 MB分で、メディアジョブが無料で再ダウンロードできます。

復元するには、同じドメインを提供するホスト上の同じパスへ展開します。スマートフォンに保存されたパスキーは、作成時の RP ID に紐付いています。そのため、新しいホスト名へ復元すると、動作するデータベースがあっても誰もサインインできません。ドメインを維持するか、すべてのパスキーを再登録する計画を立ててください。同じ方針は、ほかの運用対象にも適用できます。一般的な手順については、Docker Compose スタックのバックアップとアップグレードを参照してください。

MCP サーバーは読み取り専用で、マシン上で実行されます

MCP(model context protocol)は、Claude Desktop や Cursor などのクライアントがローカルのツールサーバーと通信するための仕組みです。openGym は mcp/ に MCP サーバーを同梱しています。これは compose ファイルの一部ではなく、コンテナでもありません。また、どのポートでも待ち受けません。クライアントが子プロセスとして起動し、stdio 経由で通信するため、README にはマシンの外部へ出ないと記載されています。

クライアントが実行される場所にインストールします。サーバーにはインストールしません。

cd openGym/mcp
npm install

次に、claude_desktop_config.json に追加します。

{
  "mcpServers": {
    "opengym": {
      "command": "node",
      "args": ["/absolute/path/to/openGym/mcp/src/index.js"],
      "env": {
        "OPENGYM_DATA": "/absolute/path/to/openGym/data",
        "OPENGYM_UID": "<your-uid>"
      }
    }
  }
}

単一ユーザーでインストールする場合、OPENGYM_UID は省略できます。この場合、サーバーは検出した唯一のプロファイルを使用します。提供するツールは list_routinesget_routineget_week_planlist_workoutsget_workoutget_bodyweightestimate_1rmmuscle_balance の 8 つです。すべて読み取り専用です。書き込みを行うツールはありません。そのため、アシスタントは先週ベンチに入れた内容には回答できますが、セットの記録、ルーティンの編集、データの削除はできません。この一覧は、エージェント設計で繰り返し確認される重要な判断を簡潔に示しています。つまり、公開したツールがモデルに実行させられる操作のすべてです。ループを自分で書いてエージェントの仕組みを学ぶと、読み取り専用のツールセットが制限ではなく設計上の選択である理由を最も早く理解できます。

VPS を利用する場合は、次の点を解決する必要があります。OPENGYM_DATA はファイルシステムのパスですが、データは VPS 上にあり、AI クライアントはノート PC 上にあります。この状況に対応する方法は 2 つあります。

  1. データをローカルへコピーし、コピーをサーバーに指定します。rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/ を実行し、OPENGYM_DATA~/opengym-data に設定します。サーバーは読み取り専用なので、コピーによってデータが失われることはありません。最新の数値が必要になったら、rsync を再実行します。
  2. commandssh に設定し、args["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"] に設定して、ssh 経由でサーバーを実行します。この方法では VPS に Node をインストールする必要があります。また、stdout に何も出力しないログインが必要です。stdout がプロトコルの通信チャネルになるためです。

どちらの方法も、agent 自体がノート PC 上で動作することを前提としています。データと同じサーバー上で実行したい場合は、OneCLI によってサーバー上でユーザーごとに隔離された agent を実行できます。そのため、data/ への stdio 接続も再びローカルになります。

cat data/db.jsonPermission denied を返す場合、API コンテナがそれらのファイルを root として作成しており、ログインユーザーが読み取れません。sudo でファイルをコピーするか、ホスト上で所有者を変更します。stdio ではなくネットワーク経由で待ち受けるサーバーについては、VPS 上で MCP サーバーを実行するを参照してください。

openGym と wger のどちらを運用すべきか

この分野では wger が定番の選択肢で、ソフトウェアの規模もはるかに大きくなります。compose スタックでは、nginx の背後で Django アプリケーションを提供する gunicorn、PostgreSQL、Redis、Celery worker が動作します。その代わり、栄養と食材の記録、文書化された REST API、規模の大きいエクササイズデータベース、他人のトレーニングプランを管理するトレーナー向け機能を利用できます。

openGym は 2 個のコンテナ、JSON ファイルを格納した 1 個のフォルダー、そして passkey 以外に管理が必要なアカウントがない構成です。違いはそれだけです。Chatwoot のインストールを運用した経験があるなら、バックアップには uploads ディレクトリと併せた Postgres ダンプが必要で、バージョン更新のたびにデータベースマイグレーションが実行されることをご存じでしょう。wger の保守も同じような構成になります。

トレーニングと併せて食事を記録したい場合や、連携先として利用する API が必要な場合は wger を運用してください。午後の時間だけで構成全体を読み通せるほど小さなスタックと、漏えいするパスワードがないログインを求める場合は openGym を運用してください。この選択には成熟度という代償があります。2026 年 8 月 19 日時点で、openGym の最初のリリースから 1 か月しか経っていません。一方、wger には何年分ものリリース実績があります。バージョンを固定し、バックアップを保持し、更新前にリリースノートを確認してください。

サーバー上で何を運用する価値があるかまだ検討中なら、2026 年にセルフホストする価値があるものでトレードオフを説明しています。このアプリは、同じ小規模 VPS 上で レシピ管理の Mealie家計管理の Actual Budget と無理なく併用できます。

何も失わずに更新する

cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api
git fetch --tags

git checkout v<new> で目的のリリースをチェックアウトし、docker compose up -d --build を実行して、そのタグからコンテナを再ビルドします。毎回、最初にバックアップを取得してください。ディスク上の JSON ファイルの復元は tar 1 つで実行でき、数秒で完了するためです。

FAQ

openGym がスマートフォンでパスキーのプロンプトを表示しないのはなぜですか?

ブラウザーが認証情報の作成を拒否しています。http:// 上、または http://192.168.1.20:8080 のような裸の IP アドレスでアクセスしていることが原因です。ブラウザーがパスキーを許可するのは HTTPS オリジンだけで、例外は localhost です。実際のホスト名に対する実際の証明書を保持するリバースプロキシの背後に openGym を配置し、RP_ID=gym.example.comORIGIN=https://gym.example.com.env に設定してから、docker compose up -d を実行してコンテナに新しい値を反映させます。プロンプトは表示されるものの、ログイン時に verification failed と表示される場合は、その 2 つの値がアドレスバーの URL と完全には一致していません。

openGym はデータをどこに保存しますか。また、どのようにバックアップしますか?

compose ファイルの隣にある ./data ディレクトリに保存され、API コンテナには /data としてマウントされます。ここには、プロファイルと公開パスキー認証情報を格納する db.json、ワークアウトと体重をユーザーごとに格納する state-<uid>.json、セッション Cookie の鍵を格納する secret、プッシュ通知の鍵を格納する vapid.json があります。docker compose stop api、次に tar czf ~/opengym-$(date +%F).tar.gz data/、続いて docker compose start api を実行し、アーカイブをサーバー外へコピーしてバックアップします。media/ は除外してください。これは 140 MB の運動画像で、media job が自動的に再ダウンロードします。

Claude は openGym のワークアウト履歴を読み取れますか?

はい。mcp/ ディレクトリにあるオプションの MCP server を介して読み取れます。ただし、読み取り専用です。ルーティン、週次プラン、記録済みワークアウト、体重、推定 1RM、筋肉のバランスを扱う 8 つのツールを公開しますが、データを書き戻すツールはありません。これはコンテナではなく、ポートも開きません。クライアントが stdio 経由で起動し、OPENGYM_DATA の JSON ファイルを直接読み取ります。これはファイルシステム上のパスを使用するため、VPS で openGym を運用する場合は、data/ のコピーをクライアントを実行するマシンへ同期するか、クライアントの設定から ssh 経由で server を呼び出します。

openGym と wger のどちらを self-host すべきですか?

トレーニング記録と併せて食事・栄養を管理したい場合や、構築の基盤にできるドキュメント化された REST API が必要な場合は、wger を選択してください。wger は、gunicorn 上の Django、PostgreSQL、Redis、Celery worker を nginx の背後で動かす、より大規模な stack です。2 つのコンテナ、cat で読み取れる JSON ファイル、そして管理するパスワードが不要なパスキー login を求める場合は、openGym を選択してください。2026 年 8 月 19 日時点で、openGym の最初の tagged release は 1 か月前のものです。そのため、git tag を checkout し、更新のたびに data/ をバックアップしてください。