איך לארח את openGym ב-Docker על שרת VPS
למדו איך לפרוס את openGym עם Docker Compose. המדריך מסביר על הצמדת גרסת git, הגדרת TLS לפני הגדרת Passkey, מיקום קובצי ה-JSON וחיבור שרת ה-MCP לקריאה בלבד בשרת מרוחק.
מה מקבלים כשמארחים את openGym באופן עצמי
אתם מארחים את openGym באופן עצמי על ידי שכפול המאגר (cloning), עריכת שתי שורות בתוך .env, והרצת docker compose up -d --build מאחורי reverse proxy שמבצע TLS termination. openGym הוא כלי למעקב אחר אימוני כושר ומשקל גוף: תוכניות שבועיות, אימונים מודרכים, רישום של כל סט, ומעקב אחר משקל לאורך זמן. התוכנה מופצת תחת רישיון AGPL-3.0 והיא שומרת את כל הנתונים בקובצי JSON פשוטים על הדיסק, כך שאין צורך להריץ שרת מסד נתונים.
ה־stack מורכב משתי מכולות (containers) שרצות ברקע: מכולת Nginx שמגישה את ה־build של React, ומכולת Node שמריצה את ה־API. בנוסף, קיים תהליך חד-פעמי שמוריד כ-140 MB של תמונות וסרטוני GIF של תרגילים בעת ההפעלה הראשונה.
ישנם שני דברים שה־README של הפרויקט רומז עליהם אך לא מפרט עבור מי שמבצע פריסה על שרת ציבורי. התחברות באמצעות Passkey כבולה לשם מתחם (hostname), לכן הדומיין והתעודה שלו חייבים להיות קיימים לפני ההתחברות הראשונה, ולא אחריה. כמו כן, שרת ה־MCP האופציונלי הוא לקריאה בלבד ורץ על המכונה שבה פועל לקוח ה-AI שלכם, ולא בתוך ה־stack; עובדה זו משנה את הפעולות הנדרשות כאשר הנתונים מאוחסנים על גבי VPS.
openGym הוא פרויקט צעיר. ה־release הראשון שקיבל תיוג, v1.0.0, מתוארך ל-20 ביולי 2026, וגרסה v1.2.7 שוחררה ב-18 באוגוסט 2026. שלושה-עשר תיוגים בחודש אחד מעידים על כך שהאפליקציה עדיין נמצאת בתהליכי פיתוח מהירים, לכן מומלץ לבצע checkout לגרסה מתויגת (release tag) במקום לבנות את מה שמופיע כרגע ב-branch ברירת המחדל.
תכנון הדומיין לפני ההתחברות הראשונה
Passkeys הם הדרך שבה מתחברים ל-openGym. מפתח Passkey קשור ל-relying party ID (או RP ID), שהוא הדומיין שבו נוצרו פרטי הזיהוי, ודפדפנים יוצרים Passkeys רק מעל HTTPS. החריג היחיד הוא localhost.
לכך יש השלכה שמשתמשים נתקלים בה בטלפון שלהם. פתחו את http://203.0.113.10:8080 ממכשיר אחר ולא תופיע כלל בקשת Passkey, כיוון שהדפדפן מסרב ליצור פרטי זיהוי במקור HTTP רגיל או בכתובת IP חשופה. הערות פתרון הבעיות של הפרויקט מציינות את אותו הדבר: היעדר בקשה אומר שאתם נמצאים ב-http:// או בכתובת IP.
גרוע מכך, ה-RP ID מוטמע בכל פרט זיהוי שהמשתמשים שלכם כבר רשמו. אם תשנו את RP_ID בשלב מאוחר יותר, ה-Passkeys השמורים במכשירים שלהם לא יתאימו יותר, ואף אחד לא יוכל להתחבר. החליטו על שם המארח (hostname) תחילה, הצביעו את ה-DNS לעבר ה-VPS, וודאו שהתעודה עובדת לפני שמישהו לוחץ על Create profile.
פריסת openGym באמצעות Docker Compose
קובץ ה-compose מבצע bind-mount ל-./data ול-./media ביחס לעצמו, לכן הספרייה שאליה אתם משכפלים את הקוד היא מסד הנתונים שלכם. מקמו אותה במיקום בעל עמידות.
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 עדיין מציג כתובת clone של github.com. כתובת זו אינה פעילה עוד, ומאגר ה-Gitea המצוין לעיל הוא הבית העדכני של הפרויקט.
ערכו את .env. בשרת VPS, יש חשיבות לשלוש שורות.
RP_ID=gym.example.com
ORIGIN=https://gym.example.com
WEB_PORT=127.0.0.1:8080RP_ID הוא שם המארח (hostname) ללא תוספות, ו-ORIGIN הוא ה-URL המלא כולל ה-scheme. עליהם להתאים בדיוק לכתובת בשורת הכתובות בדפדפן, אחרת תהליך ההתחברות ייכשל עם verification failed. הערך WEB_PORT מוסבר בסעיף העוסק בשמירה על פורט 8080 פרטי.
docker compose up -d --build
docker compose ps
docker compose logs mediadocker compose ps אמור להציג את web ואת api כפעילים (running), ואת media ככזה שיצא (exited) עם קוד 0. יציאה זו תקינה: משימת המדיה סיימה את עבודתה (restart: "no") כיוון שמדובר בהורדה חד-פעמית. הלוג שלה מסתיים בשורה המתחילה ב-✓ Exercise media ready, ו-ls media/img | wc -l אמור להציג כמה מאות קבצים ולא 0. ספרייה ריקה מעידה על כך שההורדה נכשלה, ובמקרה כזה היישום יציג כרטיסי תרגול עם תמונות ריקות.
הדגל --build אינו אופציונלי כאן. קובץ ה-compose מפנה לאימג'ים מוכנים מראש ב-ghcr.io שאינם מפורסמים עוד, לכן docker compose pull ייכשל עם denied או manifest unknown, ושני השירותים נבנים מהמקור שזה עתה שיכפלתם. שניהם כוללים סעיף build בדיוק למטרה זו. אם אתם חדשים ב-Compose, התחילו ב-Docker Compose ב-VPS וחזרו לכאן לאחר מכן.
קבעו גרסה (Pin), כיוון שהפרויקט צעיר
מכיוון שמרחב השמות ב-registry אינו קיים עוד, לא נותר תג (tag) של image שאפשר לקבע. מה שצריך לקבע במקום זאת הוא ה-checkout בדיסק, שכן הוא קובע איזו גרסה של היישום תגיע בסופו של דבר לתוך ה-container.
cd /opt/opengym
git fetch --tags
git checkout v1.2.7git status מדווח כעת על HEAD מנותק (detached) באותו תג, וזה המצב הרצוי בשרת. שום דבר לא ישתנה מתחת לידיים שלכם עד שלא תבצעו checkout לגרסה אחרת.
לאחר מכן, הורו ל-Compose להפסיק לפנות ל-registry לחלוטין. הכניסו זאת ל-docker-compose.override.yml, ש-Compose טוען אוטומטית וממזג מעל הקובץ המנוהל ב-git. מפתחות סקלריים מוחלפים על ידי ה-override, כך שאין צורך לערוך שום דבר ב-git והקובץ git pull נשאר נקי. ראו כיצד Compose ממזג קובץ override עבור כללי המיזוג המלאים.
services:
api:
pull_policy: build
web:
pull_policy: buildלאחר ביצוע פעולה זו, פקודת docker compose up -d עתידית תבנה את היישום מתוך קוד המקור שברשותכם במקום להיכשל בניסיון לבצע pull. ודאו שהמיזוג נכנס לתוקף, ולאחר מכן בצעו בנייה מחדש (rebuild) לפי התג.
docker compose config | grep pull_policy
docker compose up -d --buildסיום TLS באמצעות reverse proxy
המכולות מתקשרות ב־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. אם מופיעה הנחיית passkey ולאחר מכן ה־login מדווח על verification failed, RP_ID או ORIGIN שאינם תואמים לכתובת ה־URL בשורת הכתובות. תקנו את .env והריצו שוב את docker compose up -d, פעולה שיוצרת מחדש את המכולות כך שיקראו את הערכים החדשים. פקודת docker compose restart אינה טוענת מחדש את .env.
הגבלת הגישה לפורט 8080 מהאינטרנט הציבורי
כברירת מחדל, שירות האינטרנט מפרסם את 8080 בכל ממשקי הרשת, לכן היישום נגיש ב-HTTP גלוי דרך ה-IP הציבורי שלכם, בזמן שה-proxy מגיש HTTPS על אותו השרת. חוק firewall לא יפתור זאת. Docker מפרסם פורט באמצעות חוק DNAT בטבלת nat, והתעבורה הזו מטופלת בשרשרת FORWARD שבה החוקים של Docker עצמו מאשרים אותה, בעוד החוקים של ufw יושבים בנתיב INPUT. לכן, sudo ufw deny 8080/tcp אינו חוסם דבר.
הפתרון הוא לפרסם את הפורט בכתובת ה-loopback בלבד. קובץ ה-compose מבצע מיפוי של "${WEB_PORT:-8080}:${NGINX_PORT:-80}", כך שכל מה שתגדירו ב-WEB_PORT יוחלף בצד שמאל של המיפוי, והתחביר המקוצר של Docker מקבל שם זוג ip:port. זו הסיבה ש-WEB_PORT=127.0.0.1:8080 עובד.
docker compose config
sudo ss -ltnp | grep 8080בתצורה הממוזגת, תחת ה-ports של שירות האינטרנט, עליכם לראות host_ip: 127.0.0.1. הפקודה ss צריכה להציג 127.0.0.1:8080 ולא 0.0.0.0:8080. ממכונה אחרת, curl http://<your-vps-ip>:8080 אמורה כעת לקבל סירוב או להגיע ל-timeout, בעוד שם המתחם של ה-HTTPS ימשיך לעבוד.
סגירת ההרשמה לאחר יצירת הפרופיל שלך
ההרשמה פתוחה כברירת מחדל, ומצב אורח (guest mode) פעיל. בכתובת מארח ציבורית, המשמעות היא שכל מי שימצא את ה-URL יוכל ליצור פרופיל בשרת שלך. רשום תחילה את הפרופיל שלך, ולאחר מכן מצא את ה-user ID שלך: ls data/ מציג קובץ בשם state-<uid>.json עבור כל משתמש, וה-<uid> הזה הוא הערך שאתה צריך.
ADMIN_UIDS=<your-uid>
INVITE_ONLY=1
ALLOW_GUEST=0הרץ שוב את docker compose up -d. כעת, תחת Settings, יוצג לוח בקרה של מנהל מערכת (Admin dashboard), שבו תוכל ליצור ולבטל קודי הזמנה. כך, האנשים שאיתם אתה מתאמן יוכלו להירשם, ואף אחד אחר לא. openGym אינו מכיר ספקי זהות חיצוניים, לכן קודי ההזמנה הללו מנהלים את האפליקציה הזו בלבד ולא שום דבר אחר על השרת; אם אתה מעדיף להנפיק חשבון אחד לכל אדם עבור כל השירותים שאתה מריץ, הצבת Authentik כ-forward auth proxy תחסום את הגישה לכתובת המארח עוד לפני שמסך ה-passkey login של openGym נטען.
היכן נשמר המידע, והגיבוי שמגן עליו
כל המידע נמצא בספרייה ./data, אשר ממופה לתוך ה-container של ה-API בנתיב /data. קיימים ארבעה סוגי קבצים: db.json מכיל פרופילים ופרטי אימות של passkey ציבוריים, state-<uid>.json מכיל את השגרות, האימונים ומשקל הגוף של משתמש יחיד, secret הוא המפתח לעוגיות ה-session, ו-vapid.json מכיל את מפתחות ה-push notification שנוצרו בהרצה הראשונה.
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start apiעצרו תחילה את ה-API, כיוון ש-tar מעתיק קבצים בזמן שה-API עשוי לכתוב לאחד מהם, וקובץ JSON שהועתק באופן חלקי ישוחזר כקובץ JSON פגום. העצירה וההפעלה אורכות כשתי שניות. לאחר מכן, העתיקו את הארכיון מחוץ לשרת, שכן ארכיון שנמצא על ה-VPS לא ישרוד במקרה של קריסת ה-VPS. השאירו את media/ מחוץ לגיבוי: מדובר ב-140 MB של תמונות תרגילים שניתן להוריד שוב בחינם באמצעות משימת המדיה.
שחזור משמעו פריסת ה-tar לאותו נתיב במארח המשרת את אותו ה-domain. מפתח passkey שנשמר בטלפון שלכם מוגבל ל-RP ID שבו הוא נוצר, לכן שחזור לשם מארח (hostname) חדש ייתן לכם מסד נתונים תקין שאף אחד לא יכול להתחבר אליו. שמרו על ה-domain, או תכננו לרשום מחדש כל passkey. אותה משמעת חלה על כל דבר אחר שאתם מריצים, והמדריך גיבוי ושדרוג של stack ב-Docker Compose מכסה את השגרה הכללית.
שרת ה-MCP הוא לקריאה בלבד ורץ על המחשב שלך
MCP (ראשי תיבות של Model Context Protocol) הוא הפרוטוקול שבאמצעותו לקוח כמו Claude Desktop או Cursor מתקשר עם שרת כלים מקומי. openGym מספקת שרת כזה ב-mcp/. הוא אינו חלק מקובץ ה-compose, אינו מכולה, ואינו מאזין לאף פורט. הלקוח מפעיל אותו כתהליך בן (child process) ומתקשר איתו באמצעות 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_routines, get_routine, get_week_plan, list_workouts, get_workout, get_bodyweight, estimate_1rm ו-muscle_balance. כולם מבצעים פעולות קריאה בלבד. אף אחד מהם אינו כותב, כך שסוכן בינה מלאכותית יכול להשיב מה היה המשקל שהרמת בשבוע שעבר, אך אינו יכול לרשום סט, לערוך תוכנית אימון או למחוק דבר. רשימה זו היא דוגמה תמציתית להחלטה אחת שתכנון סוכנים חוזר אליה תמיד: הכלים שאתה חושף הם כל מה שמודל יכול לעשות, ו-למידת אופן הפעולה של סוכנים באמצעות כתיבת הלולאה בעצמך היא הדרך המהירה ביותר להבין מדוע סט כלים לקריאה בלבד הוא בחירה תכנונית ולא מגבלה.
כאן מגיע החלק שקוראי VPS צריכים לפתור. OPENGYM_DATA הוא נתיב במערכת הקבצים, והנתונים שלך נמצאים על ה-VPS בעוד לקוח ה-AI נמצא על המחשב הנייד שלך. שתי אפשרויות כנות לגבי מצב זה:
- העתק את הנתונים למחשב המקומי והפנה את השרת לעותק:
rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/, לאחר מכן הגדר אתOPENGYM_DATAל-~/opengym-data. השרת רק קורא, לכן העתק אינו גורע דבר. הרץ שוב את rsync כאשר תרצה נתונים מעודכנים. - הרץ את השרת מעל ssh, כאשר
commandמוגדר ל-sshו-argsמוגדר ל-["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"]. פעולה זו דורשת ש-Node יהיה מותקן על ה-VPS, ומשתמש שאינו מדפיס דבר ל-stdout, כיוון ש-stdout משמש כערוץ הפרוטוקול.
שתי האפשרויות מניחות שהסוכן עצמו חי על המחשב הנייד שלך. אם אתה מעדיף שהוא ירוץ על אותה מכונה שבה נמצאים הנתונים, OneCLI מעניקה לכל אדם סוכן מבודד (sandboxed) על השרת כך שדילוג ה-stdio חזרה ל-data/ הוא מקומי שוב.
אם cat data/db.json מחזיר Permission denied, מכולת ה-API כתבה את הקבצים הללו כ-root והמשתמש שלך אינו יכול לקרוא אותם. העתק אותם בעזרת sudo, או שנה את הבעלות על המארח. עבור שרתים שנועדו להאזין דרך הרשת במקום דרך stdio, ראה הרצת שרתי MCP על גבי VPS.
openGym או wger: מה כדאי להריץ?
wger היא האופציה המבוססת בתחום זה, ומדובר בתוכנה גדולה משמעותית. ה-compose stack שלה מריץ gunicorn שמגיש יישום Django, יחד עם PostgreSQL, Redis ו-Celery worker מאחורי nginx. בתמורה, אתם מקבלים מעקב אחר תזונה ורכיבים, REST API מתועד, מסד נתונים גדול של תרגילים מהקהילה, ותכונות עבור מאמנים המנהלים תוכניות אימון של אחרים.
openGym מורכבת משתי מכולות, תיקיית קובצי JSON, וללא חשבונות לניהול מעבר ל-passkeys. זהו כל ההבדל. אם יצא לכם לתחזק התקנת Chatwoot, שבה גיבוי משמעו dump של Postgres לצד תיקיית העלאות וכל עדכון גרסה מריץ מיגרציות למסד הנתונים, אתם כבר יודעים מה דורשת מכם התחזוקה של wger.
הריצו את wger אם אתם רוצים לעקוב אחר תזונה לצד אימונים, או אם אתם זקוקים ל-API כדי לפתח מולו. הריצו את openGym אם אתם רוצים stack קטן מספיק כדי לקרוא אותו מתחילתו ועד סופו בצהריים אחד, והתחברות ללא סיסמה שעלולה לדלוף. המחיר של בחירה זו הוא בשלות: נכון ל-19 באוגוסט 2026, הגרסה הראשונה של openGym בת חודש בלבד, בעוד של-wger יש שנים של גרסאות מאחוריה. קבעו גרסה (pin), שמרו גיבויים, וקראו את הערות הגרסה לפני כל עדכון.
אם אתם עדיין מחליטים מה ראוי לתפוס מקום על השרת, מה כדאי לארח באופן עצמי ב-2026 מכסה את השיקולים, והיישום הזה יושב בנוחות לצד Mealie למתכונים או Actual Budget לניהול כספים על אותו VPS קטן.
עדכון ללא אובדן נתונים
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api
git fetch --tagsבצעו checkout לגרסה הרצויה באמצעות git checkout v<new>, ולאחר מכן הריצו את docker compose up -d --build כדי שהמכולות ייבנו מחדש לפי ה-tag הזה. הגיבוי תמיד קודם לכל פעולה, כיוון ששחזור קובצי JSON מהדיסק מתבצע באמצעות פקודת tar אחת ואורך שניות בודדות.
FAQ
מדוע openGym לעולם לא מציג הנחיה למפתח גישה (passkey) בטלפון שלי?
הדפדפן מסרב ליצור אישור (credential) מכיוון שאתם נמצאים ב-http:// או בכתובת IP חשופה, כגון http://192.168.1.20:8080. דפדפנים מאפשרים שימוש במפתחות גישה רק במקורות HTTPS, כאשר localhost הוא החריג היחיד. הציבו את openGym מאחורי reverse proxy המחזיק בתעודה אמיתית עבור שם מתחם אמיתי, הגדירו את RP_ID=gym.example.com ואת ORIGIN=https://gym.example.com בתוך .env, והריצו את docker compose up -d כדי שהמכולות יטענו את הערכים החדשים. אם ההנחיה מופיעה אך ההתחברות מדווחת על verification failed, סימן ששני הערכים הללו אינם תואמים במדויק לכתובת ה-URL בשורת הכתובות.
היכן openGym שומר את הנתונים שלי, וכיצד עלי לגבות אותם?
בספרייה ./data לצד קובץ ה-compose, הממופה לתוך מכולת ה-API כ-/data. היא מכילה את db.json עבור פרופילים ואישורי מפתחות גישה ציבוריים, קובץ state-<uid>.json אחד לכל משתמש עבור אימונים ומשקל גוף, secret עבור מפתח ה-session cookie, ו-vapid.json עבור מפתחות התראות push. גבו אותם באמצעות docker compose stop api, לאחר מכן tar czf ~/opengym-$(date +%F).tar.gz data/, ולאחר מכן docker compose start api, והעתיקו את הארכיון מחוץ לשרת. דלגו על media/, המכיל 140 MB של תמונות תרגילים שמשימת המדיה מורידה שוב בעצמה.
האם Claude יכול לקרוא את היסטוריית האימונים שלי ב-openGym?
כן, דרך שרת ה-MCP האופציונלי הנמצא בספרייה mcp/, וזאת לקריאה בלבד. הוא חושף שמונה כלים המכסים שגרות אימון, תוכניות שבועיות, אימונים מתועדים, משקל גוף, הערכת משקל מקסימלי לחזרה אחת (one-rep max) ואיזון שרירים, ואף אחד מהם אינו כותב נתונים בחזרה. הוא אינו מכולה ואינו פותח פורט: הלקוח שלכם מפעיל אותו דרך stdio והוא קורא את קובצי ה-JSON ב-OPENGYM_DATA ישירות. מכיוון שמדובר בנתיב מערכת קבצים, הרצת openGym על גבי VPS משמעותה סנכרון עותק של data/ למכונה שמריצה את הלקוח, או הפעלת השרת דרך ssh מתוך הגדרות הלקוח.
האם עלי לארח בעצמי את openGym או את wger?
בחרו ב-wger אם אתם מעוניינים במעקב אחר תזונה לצד יומן האימונים שלכם, או ב-REST API מתועד לבנייה על גביו. הוא מריץ stack גדול יותר: Django תחת gunicorn, PostgreSQL, Redis ו-Celery worker מאחורי nginx. בחרו ב-openGym אם אתם מעוניינים בשתי מכולות, קובצי JSON שניתן לקרוא עם cat, והתחברות באמצעות מפתח גישה ללא צורך בניהול סיסמה. נכון ל-19 באוגוסט 2026, ה-release המתויג הראשון של openGym הוא בן חודש, לכן בדקו git tag ובצעו גיבוי ל-data/ לפני כל עדכון.