SSD Nodes Learn 🎉 VPS $5.50/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-21

Docker Compose-এ openGym নিজে host করার নিয়ম

VPS-এ openGym চালাতে pinned git tag, প্রথম passkey-এর আগেই TLS, plain JSON data কোথায় থাকে এবং stack-এর বাইরে read-only MCP server কীভাবে চলে জানুন।

openGym নিজে host করলে যা পাবেন

রিপোজিটরি clone করে, .env-এ দুটি লাইন সম্পাদনা করে এবং TLS (transport layer security) termination করা reverse proxy-এর পেছনে docker compose up -d --build চালিয়ে openGym নিজে host করবেন। openGym একটি gym ও body weight tracker: weekly plan, guided workout, প্রতিটি set-এর log এবং সময়ের সঙ্গে weight-এর পরিবর্তন। এটি AGPL-3.0 লাইসেন্সের অধীনে প্রকাশিত এবং আপনার disk-এ সবকিছু plain JSON file হিসেবে সংরক্ষণ করে। তাই আলাদা database server চালানোর প্রয়োজন নেই।

এই stack-এ দুটি দীর্ঘসময় চলমান container থাকে: একটি nginx container React build পরিবেশন করে এবং একটি Node container API পরিচালনা করে। এ ছাড়া একটি one-shot job থাকে, যা প্রথমবার চালু করার সময় প্রায় 140 MB exercise image ও GIF download করে।

Public server-এ deploy করার ক্ষেত্রে project-এর README দুটি বিষয় ইঙ্গিত করে, কিন্তু স্পষ্টভাবে ব্যাখ্যা করে না। Passkey login একটি hostname-এর সঙ্গে সংযুক্ত থাকে। তাই প্রথম login-এর আগেই domain এবং তার certificate থাকতে হবে, পরে নয়। আর ঐচ্ছিক MCP server read-only এবং এটি stack-এর ভেতরে নয়, আপনার AI client যে machine-এ চলে সেখানে চালু হয়। Data কোনো VPS-এ থাকলে এর ফলে আপনার করণীয় বদলে যায়।

openGym এখনও নতুন। প্রথম tagged release, v1.0.0, 20 July 2026 তারিখের এবং v1.2.7 18 August 2026-এ প্রকাশিত হয়েছে। প্রায় এক মাসে 13টি tag প্রকাশিত হওয়ায় বোঝা যায় যে app-টির development এখনও চলছে। তাই default branch-এ তখন যা আছে তা build না করে একটি release tag checkout করুন।

প্রথম login-এর আগেই domain পরিকল্পনা করুন

openGym-এ sign in করার পদ্ধতি হলো passkey। একটি passkey relying party ID (RP ID)-এর সঙ্গে যুক্ত থাকে। RP ID হলো যে domain-এ credential তৈরি করা হয়। Browser কেবল HTTPS-এর মাধ্যমে passkey তৈরি করে। একমাত্র ব্যতিক্রম হলো localhost

ফোনে কাজ করার সময় এই সীমাবদ্ধতা দেখা যায়। অন্য একটি device থেকে http://203.0.113.10:8080 খুললে কোনো passkey prompt দেখা যায় না। কারণ browser plain HTTP origin বা bare IP address-এ credential তৈরি করতে অস্বীকার করে। প্রকল্পের troubleshooting note-এও একই কথা বলা হয়েছে: prompt না এলে আপনি http://-এ অথবা কোনো IP-তে আছেন।

আরও গুরুত্বপূর্ণ বিষয় হলো, আপনার ব্যবহারকারীরা ইতিমধ্যে যে credential নিবন্ধন করেছেন, প্রতিটির মধ্যে RP ID স্থায়ীভাবে সংরক্ষিত থাকে। পরে RP_ID পরিবর্তন করলে তাদের device-এ সংরক্ষিত passkey আর মিলবে না। ফলে কেউ sign in করতে পারবে না। প্রথমে hostname নির্ধারণ করুন, VPS-এ DNS নির্দেশ করুন, এবং কেউ Create profile-এ tap করার আগে certificate কার্যকর করুন।

Docker Compose দিয়ে openGym স্থাপন করুন

Compose file-টি নিজের অবস্থানের আপেক্ষিক হিসেবে ./data এবং ./media bind-mount করে। তাই আপনি যে directory-তে clone করবেন, সেটিই আপনার database। directory-টি স্থায়ী storage-এ রাখুন।

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 দেওয়া আছে। ঠিকানাটি আর resolve হয় না। উপরের Gitea repository-টিই এখন project-এর সক্রিয় অবস্থান।

.env সম্পাদনা করুন। VPS-এ 3টি line গুরুত্বপূর্ণ।

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

RP_ID হলো শুধু hostname। ORIGIN হলো scheme-সহ সম্পূর্ণ URL। এগুলো address bar-এ থাকা ঠিকানার সঙ্গে হুবহু মিলতে হবে। না হলে verification failed error দিয়ে login ব্যর্থ হবে। WEB_PORT-এর মান 8080 port private রাখার section-এ ব্যাখ্যা করা হয়েছে।

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

docker compose ps-এর output-এ web এবং api running, আর media code 0 দিয়ে exited দেখানো উচিত। এই exit সঠিক। Media job-এর কাজ একবারের download হওয়ায় এটি restart: "no" করেছে। এর log-এর শেষ অংশে ✓ Exercise media ready দিয়ে শুরু হওয়া একটি line থাকে। ls media/img | wc -l চালালে 0 নয়, কয়েকশো দেখানো উচিত। Directory খালি থাকলে download ব্যর্থ হয়েছে। তখন app-এ exercise card-গুলোর image ফাঁকা দেখাবে।

এখানে --build flag বাধ্যতামূলক। Compose file-এ ghcr.io-এর prebuilt image-এর নাম দেওয়া আছে, যেগুলো আর publish করা হয় না। তাই docker compose pull চালালে denied বা manifest unknown error হয়। পরিবর্তে দুটি service-ই আপনি সদ্য clone করা source থেকে build হয়। এই কারণেই উভয় service-এ build section আছে। Compose আপনার জন্য নতুন হলে VPS-এ Docker Compose দিয়ে শুরু করে পরে এখানে ফিরে আসুন।

সংস্করণ নির্দিষ্ট করুন, কারণ এই প্রকল্পটি নতুন

ওই registry namespace আর নেই। তাই pin করার মতো কোনো image tag অবশিষ্ট নেই। এর পরিবর্তে disk-এ থাকা checkout pin করুন, কারণ container-এর মধ্যে অ্যাপ্লিকেশনের কোন version থাকবে তা এটিই নির্ধারণ করে।

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

এখন git status ওই tag-এ detached HEAD দেখাবে। server-এর ক্ষেত্রে এটাই প্রত্যাশিত অবস্থা। আপনি অন্য কোনো tag checkout না করা পর্যন্ত বর্তমান অবস্থান পরিবর্তন হবে না।

এরপর Compose-কে registry-তে আর কোনো pull request না পাঠাতে বলুন। এটি docker-compose.override.yml-এ রাখুন। Compose এই ফাইলটি স্বয়ংক্রিয়ভাবে load করে এবং tracked file-এর ওপর override হিসেবে merge করে। Scalar key-গুলো override file-এর মান দিয়ে প্রতিস্থাপিত হয়। তাই git-এ কোনো file সম্পাদনা করতে হয় না এবং git pull clean থাকে। সম্পূর্ণ merge rule-এর জন্য Compose কীভাবে override file merge করে দেখুন।

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

এটি সেট করার পর পরবর্তী docker compose up -d pull করতে ব্যর্থ না হয়ে আপনার কাছে থাকা source থেকে build করবে। Merge কার্যকর হয়েছে কি না পরীক্ষা করুন। তারপর tag-এ rebuild করুন।

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

Reverse proxy দিয়ে TLS termination

Container-গুলো plain HTTP-তে যোগাযোগ করে। Certificate সংরক্ষণের জন্য সামনে একটি component থাকতে হবে। Caddy ব্যবহার করাই সবচেয়ে সহজ পদ্ধতি, কারণ এটি নিজে থেকে Let's Encrypt থেকে certificate চায় এবং renew করে।

gym.example.com {
    reverse_proxy 127.0.0.1:8080
}

nginx, Traefik এবং Nginx Proxy Manager একইভাবে কাজ করে। Cloudflare Tunnel-ও একই কাজ করে। এই project-এ এর documentation আছে এবং এতে কোনো inbound port open রাখতে হয় না।

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

এতে certificate warning ছাড়াই HTTP/2 200 ফেরত আসা উচিত। এখন browser-এ site খুলে Create profile-এ tap করুন। Passkey prompt দেখা যাওয়ার পর login-এ verification failed দেখালে, RP_ID বা ORIGIN address bar-এর URL-এর সঙ্গে মেলে না। .env ঠিক করে আবার docker compose up -d চালান। এতে নতুন value পড়ার জন্য container-গুলো পুনরায় তৈরি হয়। একটি docker compose restart .env reload করে না।

পোর্ট 8080-কে public Internet থেকে বিচ্ছিন্ন রাখুন

ডিফল্টভাবে web service প্রতিটি interface-এ 8080 publish করে। তাই proxy একই সার্ভারে HTTPS পরিবেশন করলেও public IP ব্যবহার করে plain HTTP-তে অ্যাপে পৌঁছানো যায়। Firewall rule এটি ঠিক করে না। Docker nat table-এ DNAT rule দিয়ে একটি port publish করে। এরপর সেই traffic FORWARD chain-এ প্রক্রিয়াকৃত হয়, যেখানে Docker-এর নিজস্ব rules সেটিকে অনুমোদন করে। অন্যদিকে, ufw-এর rules INPUT path-এ থাকে। তাই sudo ufw deny 8080/tcp কোনো traffic block করে না।

সমাধান হলো শুধু loopback address-এ publish করা। Compose file-এ "${WEB_PORT:-8080}:${NGINX_PORT:-80}" mapping ব্যবহার করা হয়েছে। তাই WEB_PORT-এ আপনি যে মান সেট করবেন, সেটি mapping-এর বাম পাশে বসবে। Docker-এর short syntax সেখানে একটি ip:port pair গ্রহণ করে। এজন্য WEB_PORT=127.0.0.1:8080 কাজ করে।

docker compose config
sudo ss -ltnp | grep 8080

Merged config-এ 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 এখন connection refused দেখাবে অথবা timeout হবে। তবে HTTPS hostname কাজ করতে থাকবে।

প্রোফাইল তৈরি হলে signup বন্ধ করুন

ডিফল্টভাবে signup চালু থাকে এবং guest mode সক্রিয় থাকে। একটি public hostname-এ এর অর্থ হলো, URL খুঁজে পেলে যে কেউ আপনার সার্ভারে একটি profile তৈরি করতে পারে। প্রথমে নিজের profile register করুন। এরপর আপনার user ID খুঁজে বের করুন: ls data/ প্রতিটি user-এর জন্য state-<uid>.json নামের একটি file তালিকাভুক্ত করে, এবং সেই <uid>-এর মানটিই আপনার প্রয়োজন।

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

আবার docker compose up -d চালান। এখন Settings-এ একটি Admin dashboard দেখা যাবে। সেখানে invite code তৈরি ও বাতিল করতে পারবেন। ফলে যাদের সঙ্গে আপনি প্রশিক্ষণ নেন, তারা register করতে পারবে এবং অন্য কেউ পারবে না। openGym external identity provider সম্পর্কে কিছু জানে না। তাই ওই invite code শুধু এই app-এর জন্য প্রযোজ্য, সার্ভারের অন্য কোনো কিছুর জন্য নয়। আপনি যদি চালানো সব সেবার জন্য প্রত্যেক ব্যক্তিকে একটি করে account দিতে চান, তাহলে forward auth proxy হিসেবে Authentik সামনে বসিয়ে hostname-এ access নিয়ন্ত্রণ করতে পারেন, যাতে openGym-এর নিজস্ব passkey login লোড হওয়ার আগেই যাচাই সম্পন্ন হয়।

ডেটা কোথায় থাকে এবং কোন backup সেটিকে সুরক্ষিত রাখে

সবকিছু ./data directory-তে থাকে। এটি API container-এ /data হিসেবে mount করা আছে। এখানে চার ধরনের file আছে: db.json-এ profile এবং public passkey credential থাকে, state-<uid>.json-এ একজন user-এর routine, workout এবং body weight থাকে, secret হলো session cookie key, আর vapid.json-এ প্রথমবার চালানোর সময় তৈরি হওয়া push notification key থাকে।

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

প্রথমে API বন্ধ করুন। কারণ API কোনো file-এ লেখার সময় tar সেটি copy করতে পারে। ফলে অর্ধেক copy হওয়া JSON file restore করলে সেটি নষ্ট JSON file হিসেবে ফিরে আসে। বন্ধ করা এবং আবার চালু করতে প্রায় দুই সেকেন্ড লাগে। এরপর archive-টি server-এর বাইরে copy করুন। VPS-এ থাকা archive VPS নষ্ট হলে টিকে থাকে না। media/ backup-এ রাখবেন না। এতে 140 MB exercise image আছে, যা media job আবার বিনামূল্যে download করে নিতে পারে।

Restore করতে হলে একই domain পরিবেশন করা host-এ একই path-এর মধ্যে archive untar করতে হবে। আপনার phone-এ সংরক্ষিত passkey যে RP ID-তে তৈরি হয়েছিল, সেটির সঙ্গেই যুক্ত থাকে। তাই নতুন hostname-এ restore করলে এমন একটি কার্যকর database পাবেন, যাতে কেউ sign in করতে পারবে না। Domain অপরিবর্তিত রাখুন। না হলে প্রতিটি passkey নতুন করে register করার পরিকল্পনা করুন। আপনি চালানো অন্য সবকিছুর ক্ষেত্রেও একই নিয়ম প্রযোজ্য। সাধারণ পদ্ধতির জন্য Docker Compose stack-এর backup নেওয়া ও upgrade করা দেখুন।

MCP server শুধু read-only, এবং এটি আপনার machine-এ চলে

MCP (model context protocol) হলো Claude Desktop বা Cursor-এর মতো client-এর local tool server-এর সঙ্গে যোগাযোগের পদ্ধতি। openGym এটি mcp/-এ সরবরাহ করে। এটি compose file-এর অংশ নয়, container নয়, এবং কোনো port-এ listening করে না। client এটিকে child process হিসেবে চালায় এবং stdio-এর মাধ্যমে যোগাযোগ করে। তাই README-তে বলা হয়েছে, এটি কখনো আপনার machine ছেড়ে যায় না।

client যেখানে চলে, সেখানে এটি install করুন, server-এ নয়:

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>"
      }
    }
  }
}

একজন user-এর install-এ OPENGYM_UID ঐচ্ছিক, কারণ সেখানে server যে একমাত্র profile খুঁজে পায়, সেটি নিজেই শনাক্ত করে। এটি আটটি tool প্রকাশ করে: list_routines, get_routine, get_week_plan, list_workouts, get_workout, get_bodyweight, estimate_1rm এবং muscle_balance। প্রতিটি tool শুধু data পড়ে। কোনো tool data লেখে না। তাই assistant গত সপ্তাহে আপনি কী bench করেছেন তা বলতে পারে, কিন্তু কোনো set log করতে, routine সম্পাদনা করতে বা কিছু delete করতে পারে না।

VPS ব্যবহারকারীর জন্য সমাধান করার বিষয়টি এখানে। OPENGYM_DATA একটি filesystem path, কিন্তু আপনার data VPS-এ এবং AI client আপনার laptop-এ চলছে। এই পরিস্থিতির জন্য দুটি বাস্তবসম্মত option আছে।

  1. Data local machine-এ copy করে server-কে সেই copy ব্যবহার করতে দিন: rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/, তারপর OPENGYM_DATA-কে ~/opengym-data হিসেবে সেট করুন। Server শুধু data পড়ে, তাই copy করলে কোনো data হারায় না। নতুন data পেতে চাইলে আবার rsync চালান।
  2. command-কে ssh এবং args-কে ["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"] হিসেবে সেট করে server-টি ssh-এর মাধ্যমে চালান। এর জন্য VPS-এ Node install থাকতে হবে। যে login stdout-এ কিছুই print করে না, সেটিও প্রয়োজন, কারণ stdout হলো protocol channel।

cat data/db.json যদি Permission denied ফেরত দেয়, তাহলে API container ওই file-গুলো root হিসেবে লিখেছে এবং আপনার login সেগুলো পড়তে পারছে না। sudo ব্যবহার করে file-গুলো copy করুন, অথবা host-এ ownership পরিবর্তন করুন। stdio-এর পরিবর্তে network-এর মাধ্যমে listening করার জন্য server চালাতে হলে VPS-এ MCP server চালানো দেখুন।

openGym না wger: কোনটি চালাবেন?

এই নির্দিষ্ট ক্ষেত্রে wger একটি প্রতিষ্ঠিত বিকল্প, এবং এটি আকারেও অনেক বড় software। এর compose stack-এ gunicorn একটি Django application চালায়, সঙ্গে থাকে PostgreSQL, Redis এবং nginx-এর পেছনে একটি Celery worker। এর বিনিময়ে আপনি nutrition ও ingredient tracking, একটি documented REST API, exercise-এর বড় community database এবং অন্যদের training plan পরিচালনার জন্য trainer-দের উপযোগী feature পান।

openGym-এ রয়েছে দুটি container, JSON file-এর একটি folder এবং passkey ছাড়া পরিচালনা করার মতো কোনো account নেই। পার্থক্যটি মূলত এতটুকুই।

Training-এর পাশাপাশি food track করতে চাইলে, অথবা যার ওপর ভিত্তি করে application তৈরি করার জন্য API প্রয়োজন হলে wger চালান। এমন একটি stack চাইলে openGym চালান, যার সম্পূর্ণ code এক বিকেলে শুরু থেকে শেষ পর্যন্ত পড়ে বোঝা যায় এবং যে login-এ ফাঁস হওয়ার মতো কোনো password নেই। এই পছন্দের বিনিময়ে আপনাকে maturity ত্যাগ করতে হবে: 19 August 2026 অনুযায়ী openGym-এর প্রথম release-এর বয়স এক মাস, আর wger-এর পেছনে রয়েছে বহু বছরের release। আপনার version pin করুন, backup সংরক্ষণ করুন এবং প্রতিটি update-এর আগে release note পড়ুন।

box-এ কোন software-এর জন্য জায়গা রাখা উপযোগী তা নিয়ে এখনও সিদ্ধান্ত না নিলে, 2026 সালে self-host করার মতো কী আছে trade-off-গুলো ব্যাখ্যা করে। একই ছোট VPS-এ এই app-টি recipe-এর জন্য 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> দিয়ে পছন্দের release checkout করুন। এরপর docker compose up -d --build চালান, যাতে ওই tag থেকে container-গুলো পুনর্নির্মাণ করা হয়। প্রতিবার আগে backup নিন, কারণ disk-এ থাকা JSON file পুনরুদ্ধারের জন্য tar কমান্ডই যথেষ্ট এবং এতে কয়েক সেকেন্ড লাগে।

FAQ

openGym আমার ফোনে passkey prompt দেখায় না কেন?

আপনি http:// অথবা http://192.168.1.20:8080-এর মতো bare IP address ব্যবহার করছেন বলে browser credential তৈরি করতে দিচ্ছে না। Browser কেবল HTTPS origin-এ passkey অনুমোদন করে; একমাত্র ব্যতিক্রম হলো localhost। openGym-কে real hostname-এর জন্য real certificate ব্যবহার করা reverse proxy-এর পেছনে রাখুন। .env-এ RP_ID=gym.example.com এবং ORIGIN=https://gym.example.com সেট করুন। নতুন value container-গুলোতে প্রয়োগ করতে docker compose up -d চালান। Prompt দেখা গেলেও login-এ verification failed দেখালে, ওই দুইটি value address bar-এর URL-এর সঙ্গে হুবহু মেলে না।

openGym আমার data কোথায় সংরক্ষণ করে, এবং আমি কীভাবে backup নেব?

Compose file-এর পাশে থাকা ./data directory-তে data সংরক্ষিত হয়। এই directory API container-এ /data হিসেবে mount করা থাকে। এখানে profile ও public passkey credential-এর জন্য db.json, প্রতিটি user-এর workout ও body weight-এর জন্য একটি করে state-<uid>.json, session cookie key-এর জন্য secret এবং push notification key-এর জন্য vapid.json থাকে। প্রথমে docker compose stop api, তারপর tar czf ~/opengym-$(date +%F).tar.gz data/, তারপর docker compose start api ব্যবহার করে backup নিন। এরপর archive-টি server-এর বাইরে copy করুন। media/ বাদ দিন। এতে 140 MB exercise image থাকে, যা media job নিজে আবার download করে।

Claude কি আমার openGym workout history পড়তে পারে?

হ্যাঁ। এটি mcp/ directory-তে থাকা ঐচ্ছিক MCP server-এর মাধ্যমে পড়তে পারে, তবে কেবল read-only access থাকে। Server-টি routine, week plan, logged workout, body weight, estimated one-rep max এবং muscle balance নিয়ে মোট আটটি tool প্রকাশ করে। কোনো tool-ই data লিখে পরিবর্তন করে না। এটি container নয় এবং কোনো port খোলে না। আপনার client এটি stdio-এর মাধ্যমে start করে এবং সরাসরি OPENGYM_DATA-এর JSON file পড়ে। এটি filesystem path হওয়ায় VPS-এ openGym চালালে client চালানো machine-এ data/-এর একটি copy sync করতে হবে। বিকল্পভাবে, client config থেকে ssh ব্যবহার করে server invoke করুন।

আমার কি openGym নাকি wger self-host করা উচিত?

Training log-এর পাশাপাশি food ও nutrition tracking চাইলে, অথবা এর ওপর ভিত্তি করে কাজ করার জন্য documented REST API প্রয়োজন হলে wger বেছে নিন। এটি বড় stack চালায়: gunicorn-এর অধীনে Django, PostgreSQL, Redis এবং nginx-এর পেছনে একটি Celery worker। দুইটি container, cat দিয়ে পড়া যায় এমন JSON file এবং manage করার মতো কোনো password ছাড়া passkey login চাইলে openGym বেছে নিন। 19 August 2026 অনুযায়ী openGym-এর প্রথম tagged release এক মাস পুরোনো। তাই প্রতিটি update-এর আগে একটি git tag checkout করুন এবং data/ backup নিন।