Cara Self-Host openGym dengan Docker Compose
Deploy openGym di VPS dengan Docker Compose: gunakan tag git yang dipatok, siapkan TLS sebelum passkey pertama, ketahui lokasi data, dan aktifkan MCP read-only.
Yang Anda dapatkan saat men-deploy openGym sendiri
Anda men-deploy openGym sendiri dengan melakukan clone repository, mengedit dua baris dalam .env, lalu menjalankan docker compose up -d --build di belakang reverse proxy yang melakukan TLS termination (transport layer security). openGym adalah pelacak gym dan berat badan: rencana mingguan, latihan terpandu, setiap set dicatat, serta perubahan berat badan dari waktu ke waktu. Lisensinya adalah AGPL-3.0, dan semua data disimpan dalam file JSON biasa di disk Anda, sehingga Anda tidak perlu menjalankan server database.
Stack ini terdiri atas dua container yang berjalan terus-menerus: container nginx yang menyajikan build React dan container Node yang menjalankan API, serta satu job sekali jalan yang mengunduh sekitar 140 MB gambar latihan dan GIF saat pertama kali Anda menjalankannya.
README proyek ini mengisyaratkan dua hal, tetapi tidak menjelaskannya secara eksplisit bagi orang yang melakukan deployment pada server publik. Login dengan passkey terikat pada hostname, sehingga domain dan sertifikatnya harus sudah tersedia sebelum login pertama, bukan setelahnya. Selain itu, server MCP opsional bersifat read-only dan berjalan pada mesin tempat klien AI Anda berjalan, bukan di dalam stack. Hal ini mengubah langkah yang perlu dilakukan ketika data berada pada VPS.
openGym masih tergolong baru. Rilis bertag pertama, v1.0.0, bertanggal 20 July 2026, dan v1.2.7 dirilis pada 18 August 2026. Tiga belas tag dalam waktu sekitar satu bulan menunjukkan bahwa aplikasi ini masih sering berubah. Karena itu, checkout release tag, bukan membangun versi apa pun yang kebetulan berada pada default branch.
Rencanakan domain sebelum login pertama
Passkey adalah cara Anda masuk ke openGym. Passkey terikat pada relying party ID (RP ID), yaitu domain tempat kredensial dibuat, dan browser hanya membuat passkey melalui HTTPS. Satu-satunya pengecualian adalah localhost.
Hal ini menimbulkan masalah yang sering ditemui pengguna pada ponsel. Buka http://203.0.113.10:8080 dari perangkat lain, dan permintaan passkey sama sekali tidak muncul karena browser menolak membuat kredensial pada origin HTTP biasa atau alamat IP tanpa hostname. Catatan pemecahan masalah proyek juga menyebutkan hal yang sama: jika permintaan tidak muncul, berarti Anda menggunakan http:// atau alamat IP.
Yang lebih buruk, RP ID tertanam dalam setiap kredensial yang telah didaftarkan pengguna. Jika Anda mengubah RP_ID nanti, passkey yang tersimpan di perangkat mereka tidak lagi cocok sehingga tidak seorang pun dapat masuk. Tentukan hostname terlebih dahulu, arahkan DNS ke VPS, dan pastikan sertifikat berfungsi sebelum siapa pun mengetuk Create profile.
Deploy openGym dengan Docker Compose
File compose memasang ./data dan ./media sebagai bind mount relatif terhadap file itu sendiri. Karena itu, direktori tempat Anda melakukan clone adalah database Anda. Simpan direktori tersebut di lokasi yang persisten.
sudo install -d -o "$USER" -g "$USER" /opt/opengym
git clone https://gitea.com/DuarteSantos/openGym /opt/opengym
cd /opt/opengym
cp .env.example .envREADME masih menampilkan URL clone github.com. Alamat tersebut sudah tidak dapat diakses, dan repositori Gitea di atas merupakan lokasi aktif proyek ini.
Edit .env. Pada VPS, tiga baris perlu diperhatikan.
RP_ID=gym.example.com
ORIGIN=https://gym.example.com
WEB_PORT=127.0.0.1:8080RP_ID adalah hostname tanpa skema, sedangkan ORIGIN adalah URL lengkap yang mencakup skema. Keduanya harus sama persis dengan alamat pada bilah alamat browser. Jika tidak, login gagal dengan verification failed. Nilai WEB_PORT dijelaskan pada bagian tentang menjaga port 8080 tetap privat.
docker compose up -d --build
docker compose ps
docker compose logs mediadocker compose ps seharusnya menampilkan web dan api sebagai service yang sedang berjalan, serta media sebagai service yang berhenti dengan kode 0. Status berhenti tersebut benar: job media telah restart: "no" karena tugasnya hanya mengunduh satu kali. Log-nya berakhir dengan baris yang diawali ✓ Exercise media ready, dan ls media/img | wc -l seharusnya menampilkan beberapa ratus file, bukan 0. Direktori yang kosong berarti pengunduhan gagal. Akibatnya, aplikasi menampilkan kartu latihan dengan gambar kosong.
Flag --build wajib digunakan di sini. File compose menyebut image siap pakai pada ghcr.io, tetapi image tersebut sudah tidak dipublikasikan. Karena itu, docker compose pull gagal dengan denied atau manifest unknown, lalu kedua service tersebut dibuat dari source code yang baru saja Anda clone. Keduanya memiliki bagian build khusus untuk tujuan tersebut. Jika Compose masih baru bagi Anda, mulai dari Docker Compose pada VPS, lalu kembali ke bagian ini.
Kunci versinya karena proyek ini masih baru
Karena namespace registry tersebut sudah tidak tersedia, tidak ada tag image yang dapat dikunci. Sebagai gantinya, kunci checkout pada disk karena checkout tersebut menentukan versi aplikasi yang masuk ke dalam container.
cd /opt/opengym
git fetch --tags
git checkout v1.2.7git status sekarang melaporkan HEAD detached pada tag tersebut. Kondisi ini sesuai untuk server. Tidak ada perubahan sampai Anda melakukan checkout ke versi lain.
Selanjutnya, beri tahu Compose agar tidak lagi mengakses registry. Masukkan konfigurasi ini ke docker-compose.override.yml, yang dimuat Compose secara otomatis dan digabungkan di atas file yang dilacak. Key skalar akan digantikan oleh override, sehingga tidak ada yang perlu diedit di git dan git pull tetap bersih. Lihat cara Compose menggabungkan file override untuk aturan penggabungan lengkap.
services:
api:
pull_policy: build
web:
pull_policy: buildDengan konfigurasi tersebut, docker compose up -d berikutnya akan membangun image dari source yang tersedia, bukan gagal saat melakukan pull. Periksa apakah penggabungan sudah diterapkan, lalu lakukan build ulang pada tag tersebut.
docker compose config | grep pull_policy
docker compose up -d --buildAkhiri TLS dengan reverse proxy
Container berkomunikasi menggunakan HTTP biasa. Komponen di depannya harus menyimpan sertifikat. Caddy merupakan cara paling singkat karena dapat meminta dan memperbarui sertifikat dari Let's Encrypt secara mandiri.
gym.example.com {
reverse_proxy 127.0.0.1:8080
}nginx, Traefik, dan Nginx Proxy Manager juga bekerja dengan cara yang sama. Cloudflare Tunnel juga demikian. Proyek ini mendokumentasikannya, dan metode tersebut sama sekali tidak memerlukan port masuk yang terbuka.
curl -sI https://gym.example.com | head -1Perintah tersebut harus mengembalikan HTTP/2 200 tanpa peringatan sertifikat. Sekarang buka situs di browser, lalu pilih Create profile. Jika permintaan passkey muncul, tetapi login kemudian melaporkan verification failed, berarti RP_ID atau ORIGIN tidak cocok dengan URL pada bilah alamat. Perbaiki .env, lalu jalankan kembali docker compose up -d. Perintah tersebut membuat ulang container agar membaca nilai baru. docker compose restart tidak memuat ulang .env.
Jaga agar port 8080 tidak dapat diakses dari Internet publik
Secara default, layanan web memublikasikan 8080 pada setiap antarmuka. Akibatnya, aplikasi dapat diakses melalui HTTP biasa pada IP publik Anda, sementara proxy melayani HTTPS pada server yang sama. Aturan firewall tidak memperbaiki masalah ini. Docker memublikasikan port dengan aturan DNAT di tabel nat, kemudian trafik tersebut diproses dalam chain FORWARD, tempat aturan milik Docker sendiri menerimanya. Sementara itu, aturan ufw berada pada jalur INPUT. Karena itu, sudo ufw deny 8080/tcp tidak memblokir apa pun.
Solusinya adalah memublikasikan port hanya pada alamat loopback. File compose memetakan "${WEB_PORT:-8080}:${NGINX_PORT:-80}". Jadi, nilai yang Anda tetapkan di WEB_PORT menggantikan bagian kiri pemetaan tersebut, dan sintaks singkat Docker menerima pasangan ip:port di sana. Karena itu, WEB_PORT=127.0.0.1:8080 berfungsi.
docker compose config
sudo ss -ltnp | grep 8080Dalam konfigurasi gabungan, di bawah ports milik layanan web, Anda harus melihat host_ip: 127.0.0.1. ss harus menampilkan 127.0.0.1:8080, bukan 0.0.0.0:8080. Dari komputer lain, curl http://<your-vps-ip>:8080 kini seharusnya ditolak atau mengalami timeout, sedangkan hostname HTTPS tetap berfungsi.
Tutup pendaftaran setelah profil Anda dibuat
Pendaftaran terbuka secara default, dan mode tamu aktif. Pada hostname publik, siapa pun yang menemukan URL tersebut dapat membuat profil di server Anda. Daftarkan profil Anda sendiri terlebih dahulu, lalu cari ID pengguna Anda: ls data/ menampilkan file bernama state-<uid>.json untuk setiap pengguna, dan <uid> tersebut adalah nilai yang Anda perlukan.
ADMIN_UIDS=<your-uid>
INVITE_ONLY=1
ALLOW_GUEST=0Jalankan docker compose up -d lagi. Settings sekarang menampilkan dasbor Admin yang dapat digunakan untuk membuat dan mencabut kode undangan, sehingga orang yang berlatih bersama Anda dapat mendaftar dan orang lain tidak dapat mendaftar. openGym tidak mengenal penyedia identitas eksternal, sehingga kode undangan tersebut hanya berlaku untuk aplikasi ini dan tidak memengaruhi layanan lain di server; jika Anda ingin memberikan satu akun kepada setiap orang untuk semua layanan yang Anda jalankan, pasang Authentik di depan sebagai proxy forward auth untuk membatasi akses ke hostname sebelum login passkey bawaan openGym dimuat.
Lokasi data dan pencadangan yang melindunginya
Semua data berada di direktori ./data, yang di-mount ke container API pada /data. Ada empat jenis file: db.json menyimpan profil dan kredensial passkey publik, state-<uid>.json menyimpan rutinitas, latihan, dan berat badan satu pengguna, secret adalah kunci cookie sesi, dan vapid.json menyimpan kunci notifikasi push yang dibuat saat pertama kali dijalankan.
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start apiHentikan API terlebih dahulu karena tar menyalin file saat API mungkin sedang menulisnya. File JSON yang hanya tersalin sebagian akan dipulihkan sebagai file JSON yang rusak. Proses berhenti dan mulai kembali memerlukan waktu sekitar dua detik. Setelah itu, salin arsip keluar dari server karena arsip yang hanya berada di VPS tidak akan bertahan jika VPS mengalami kerusakan. Jangan sertakan media/ dalam pencadangan. Direktori tersebut berukuran 140 MB dan berisi gambar latihan yang dapat diunduh kembali oleh media job tanpa biaya tambahan.
Pemulihan berarti mengekstrak arsip ke path yang sama pada host yang melayani domain yang sama. Passkey yang disimpan di ponsel terikat pada RP ID tempat passkey tersebut dibuat. Karena itu, pemulihan ke hostname baru menghasilkan database yang berfungsi, tetapi tidak ada pengguna yang dapat login. Pertahankan domain tersebut, atau rencanakan pendaftaran ulang semua passkey. Prinsip yang sama berlaku untuk semua layanan lain yang Anda jalankan. Pencadangan dan upgrade stack Docker Compose membahas prosedur umumnya.
Server MCP bersifat hanya-baca dan berjalan di mesin Anda
MCP (model context protocol) adalah cara client seperti Claude Desktop atau Cursor berkomunikasi dengan server alat lokal. openGym menyertakan salah satunya di mcp/. Komponen ini bukan bagian dari file compose, bukan container, dan tidak mendengarkan port apa pun. Client menjalankannya sebagai proses anak dan berkomunikasi melalui stdio. Karena itu, README menyatakan bahwa komponen ini tidak pernah meninggalkan mesin Anda.
Instal komponen ini di tempat client berjalan, bukan di server:
cd openGym/mcp
npm installKemudian tambahkan ke 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 bersifat opsional pada instalasi pengguna tunggal, karena server mendeteksi satu-satunya profil yang ditemukan. Komponen ini menyediakan delapan alat: list_routines, get_routine, get_week_plan, list_workouts, get_workout, get_bodyweight, estimate_1rm, dan muscle_balance. Semua alat tersebut hanya membaca. Tidak ada yang menulis. Dengan demikian, assistant dapat menjawab latihan apa yang Anda lakukan minggu lalu, tetapi tidak dapat mencatat satu set, mengubah routine, atau menghapus apa pun. Daftar ini merupakan contoh ringkas dari prinsip dalam desain agent, yaitu bahwa alat yang Anda ekspos menentukan seluruh kemampuan model. Mempelajari cara kerja agent dengan menulis loop sendiri adalah cara tercepat untuk memahami bahwa kumpulan alat hanya-baca merupakan pilihan desain, bukan keterbatasan.
Berikut bagian yang perlu diselesaikan oleh pengguna VPS. OPENGYM_DATA adalah path filesystem, sedangkan data Anda berada di VPS dan client AI Anda berada di laptop. Berikut dua opsi yang sesuai dengan kondisi tersebut.
- Salin data ke laptop dan arahkan server ke salinan tersebut:
rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/, lalu aturOPENGYM_DATAke~/opengym-data. Server hanya membaca data, jadi penyalinan tidak menghilangkan data apa pun. Jalankan kembali rsync jika Anda memerlukan angka terbaru. - Jalankan server melalui ssh, dengan
commanddiatur kesshdanargsdiatur ke["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"]. Node harus terinstal di VPS. Selain itu, login tidak boleh mencetak apa pun ke stdout karena stdout digunakan sebagai channel protokol.
Kedua opsi tersebut mengasumsikan bahwa agent berjalan di laptop Anda. Jika agent sebaiknya berjalan pada server yang sama dengan data, OneCLI menyediakan agent terisolasi untuk setiap orang pada server sehingga hop stdio kembali ke data/ kembali bersifat lokal.
Jika cat data/db.json mengembalikan Permission denied, container API menulis file tersebut sebagai root sehingga login Anda tidak dapat membacanya. Salin file tersebut dengan sudo, atau ubah ownership-nya pada host. Untuk server yang dirancang agar mendengarkan melalui jaringan, bukan melalui stdio, lihat menjalankan server MCP pada VPS.
openGym atau wger: mana yang sebaiknya Anda jalankan?
wger adalah pilihan yang sudah mapan di bidang ini dan merupakan perangkat lunak yang jauh lebih besar. Compose stack-nya menjalankan gunicorn yang melayani aplikasi Django, PostgreSQL, Redis, dan worker Celery di belakang nginx. Sebagai gantinya, Anda mendapatkan pelacakan nutrisi dan bahan makanan, REST API yang terdokumentasi, database latihan dengan banyak data, serta fitur bagi pelatih untuk mengelola program orang lain.
openGym terdiri atas dua container, satu folder berisi file JSON, dan tidak memerlukan akun yang harus dikelola selain passkey. Hanya itu perbedaannya.
Jalankan wger jika Anda ingin melacak makanan bersama latihan atau memerlukan API untuk membangun aplikasi. Jalankan openGym jika Anda menginginkan stack yang cukup kecil untuk dibaca seluruhnya dalam satu sore, serta login tanpa kata sandi yang dapat bocor. Konsekuensi pilihan ini adalah tingkat kematangan: per 19 August 2026, rilis pertama openGym baru berusia satu bulan, sedangkan wger telah memiliki riwayat rilis selama bertahun-tahun. Tetapkan versi yang digunakan, simpan backup, dan baca catatan rilis sebelum setiap pembaruan.
Jika Anda masih menentukan layanan yang layak menggunakan ruang pada server, apa yang layak di-self-host pada 2026 membahas berbagai komprominya. Aplikasi ini juga cocok dijalankan berdampingan dengan Mealie untuk resep atau Actual Budget untuk keuangan pada VPS kecil yang sama.
Memperbarui tanpa kehilangan data
cd /opt/opengym
docker compose stop api
tar czf ~/opengym-$(date +%F).tar.gz data/
docker compose start api
git fetch --tagsCheckout rilis yang diinginkan dengan git checkout v<new>, lalu jalankan docker compose up -d --build agar container dibangun ulang dari tag tersebut. Backup selalu dilakukan terlebih dahulu, karena proses pemulihan file JSON di disk hanya memerlukan satu perintah tar dan selesai dalam hitungan detik.
FAQ
Mengapa openGym tidak pernah menampilkan permintaan passkey di ponsel saya?
Browser menolak membuat kredensial karena Anda menggunakan http:// atau alamat IP langsung, seperti http://192.168.1.20:8080. Browser hanya mengizinkan passkey pada origin HTTPS, dengan localhost sebagai satu-satunya pengecualian. Letakkan openGym di belakang reverse proxy yang memiliki sertifikat valid untuk hostname yang valid, tetapkan RP_ID=gym.example.com dan ORIGIN=https://gym.example.com di .env, lalu jalankan docker compose up -d agar container menerapkan nilai baru. Jika permintaan muncul tetapi login melaporkan verification failed, kedua nilai tersebut tidak sama persis dengan URL pada bilah alamat.
Di mana openGym menyimpan data saya, dan bagaimana cara mencadangkannya?
Data disimpan di direktori ./data yang berada di sebelah file compose, lalu di-mount ke container API sebagai /data. Direktori tersebut berisi db.json untuk profil dan kredensial passkey publik, satu state-<uid>.json per pengguna untuk data latihan dan berat badan, secret untuk kunci cookie sesi, serta vapid.json untuk kunci notifikasi push. Cadangkan data dengan docker compose stop api, kemudian tar czf ~/opengym-$(date +%F).tar.gz data/, lalu docker compose start api, dan salin arsip tersebut keluar dari server. Jangan sertakan media/, yang berukuran 140 MB dan berisi gambar latihan yang akan diunduh kembali oleh media job secara otomatis.
Apakah Claude dapat membaca riwayat latihan openGym saya?
Ya, melalui MCP server opsional di direktori mcp/, dan hanya untuk membaca data. Server tersebut menyediakan delapan tool untuk rutinitas, rencana mingguan, latihan yang dicatat, berat badan, estimasi one-rep max, dan keseimbangan otot. Tidak satu pun tool tersebut menulis kembali data. Server ini bukan container dan tidak membuka port. Client menjalankannya melalui stdio, lalu server membaca file JSON langsung dari OPENGYM_DATA. Karena yang digunakan adalah path filesystem, menjalankan openGym pada VPS berarti Anda harus menyinkronkan salinan data/ ke mesin yang menjalankan client, atau memanggil server melalui ssh dari konfigurasi client.
Sebaiknya saya melakukan self-hosting openGym atau wger?
Pilih wger jika Anda ingin melacak makanan dan nutrisi selain mencatat latihan, atau membutuhkan REST API terdokumentasi sebagai dasar pengembangan. wger menjalankan stack yang lebih besar: Django di bawah gunicorn, PostgreSQL, Redis, dan worker Celery di belakang nginx. Pilih openGym jika Anda menginginkan dua container, file JSON yang dapat dibaca dengan cat, serta login passkey tanpa perlu mengelola kata sandi. Per 19 August 2026, rilis bertag pertama openGym baru berusia satu bulan. Karena itu, checkout tag git dan cadangkan data/ sebelum setiap pembaruan.