Cara Self-Host openGym dengan Docker Compose
Deploy openGym di VPS dengan Docker Compose: gunakan git tag yang dipatok, siapkan TLS sebelum passkey pertama, pahami lokasi data, dan MCP server read-only.
Yang Anda dapatkan saat melakukan self-hosting openGym
Anda melakukan self-hosting openGym dengan meng-clone repositori, mengedit dua baris di .env, lalu menjalankan docker compose up -d --build di belakang reverse proxy yang menangani TLS (transport layer security). openGym adalah pelacak aktivitas gym dan berat badan: rencana mingguan, latihan terpandu, setiap set tercatat, 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 tidak ada database server yang perlu dijalankan.
Stack ini terdiri atas dua container yang berjalan terus-menerus: container nginx yang menyajikan build React dan container Node yang menjalankan API, serta sebuah job sekali jalan yang mengunduh sekitar 140 MB gambar dan GIF latihan saat pertama kali Anda menjalankannya.
README proyek ini menyiratkan 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 tersedia sebelum login pertama, bukan setelahnya. Selain itu, MCP server opsional bersifat hanya-baca dan berjalan pada mesin tempat client AI Anda berjalan, bukan di dalam stack. Hal ini mengubah langkah yang perlu dilakukan ketika data berada di VPS.
openGym masih baru. Rilis bertag pertama, v1.0.0, bertanggal 20 July 2026, dan v1.2.7 dirilis pada 18 August 2026. Tiga belas tag pada bulan pertamanya dan 34 tag pada 9 October 2026, dengan v1.4.0 ditandai pada tanggal tersebut, menunjukkan bahwa aplikasi ini masih aktif dikembangkan. Karena itu, checkout release tag, bukan apa pun yang kebetulan ada pada default branch.
Rencanakan domain sebelum login pertama
Passkey digunakan untuk 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 sering ditemukan saat pengguna mengaksesnya melalui ponsel. Buka http://203.0.113.10:8080 dari perangkat lain dan tidak ada prompt passkey yang muncul sama sekali, karena browser menolak membuat kredensial pada origin HTTP biasa atau alamat IP tanpa domain. Catatan pemecahan masalah proyek juga menyebutkan hal yang sama: tidak adanya prompt berarti Anda menggunakan http:// atau alamat IP.
Lebih buruk lagi, RP ID tertanam dalam setiap kredensial yang telah didaftarkan pengguna. Jika RP_ID diubah kemudian, 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 melakukan bind-mount ./data dan ./media secara relatif terhadap file tersebut. Jadi, direktori tempat Anda melakukan clone adalah database Anda. Letakkan direktori itu 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
git checkout v1.2.7
cp .env.example .envREADME menampilkan URL clone github.com/DuarteSantos8/openGym. Per 9 October 2026, repository tersebut kembali dapat diakses dan menyatakan dirinya sebagai beranda proyek. Sementara itu, repository Gitea di atas masih diperbarui dan memuat semua tag. Jadi, keduanya dapat digunakan. Checkout tag tersebut sebelum build pertama. Branch default sudah melewati versi yang dijelaskan dalam panduan ini. Pada branch tersebut, animasi latihan sudah disertakan di dalam image dan job media di bawah ini sudah tidak ada. Bagian tentang pinning menjelaskan alasannya.
Edit .env. Pada VPS, ada tiga baris yang penting.
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 address bar. 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 --all
docker compose logs mediadocker compose ps --all seharusnya menampilkan web dan api dengan status running, serta media dengan status exited dan code 0. Pertahankan flag --all. Tanpa flag tersebut, Compose hanya menampilkan container yang sedang berjalan, sehingga job media yang sudah selesai tidak muncul sama sekali. Status keluar tersebut benar. Job media memiliki restart: "no" karena tugasnya adalah mengunduh data satu kali. Log-nya berakhir dengan baris yang diawali ✓ Exercise media ready, dan ls media/img | wc -l seharusnya mencetak 1,324, bukan 0. Angka tersebut adalah jumlah thumbnail yang disertakan upstream exercise dataset per 9 October 2026. Direktori yang kosong berarti pengunduhan gagal. Akibatnya, aplikasi menampilkan kartu latihan dengan gambar kosong.
Pertahankan flag --build. File compose pada tag ini menetapkan image bawaan di ghcr.io dengan tag latest. Image tersebut kembali dipublikasikan per 9 October 2026. Namun, latest mengikuti release terbaru, sehingga docker compose pull akan menjalankan kode yang lebih baru daripada source yang baru saja Anda checkout. Kedua service memiliki bagian build, dan --build akan melakukan build dari checkout Anda. Jika Compose masih baru bagi Anda, mulai dengan Docker Compose pada VPS, lalu kembali ke sini.
Tetapkan versinya karena proyek ini masih baru
Registry kembali menyediakan image berversi, termasuk tag 1.2.7 per 9 October 2026. Namun, file Compose pada v1.2.7 masih meminta latest, yang pada hari itu menunjuk ke 1.3.10. Yang Anda tetapkan versinya adalah checkout di disk, karena checkout tersebut menentukan versi aplikasi yang masuk ke dalam container.
cd /opt/opengym
git fetch --tags
git checkout v1.2.7git status kini melaporkan HEAD terlepas pada tag tersebut. Ini adalah kondisi yang diinginkan pada server. Tidak ada perubahan hingga Anda melakukan checkout ke versi lain.
v1.2.7 adalah rilis yang menjadi acuan penulisan panduan ini dan bukan lagi rilis terbaru. Rilis yang lebih baru mengubah deployment itu sendiri. Pada branch default, job media sudah dihapus. Karena itu, baca catatan rilis dan bandingkan file Compose sebelum mengubah versi yang ditetapkan.
Selanjutnya, beri tahu Compose agar sama sekali tidak mengambil image dari registry. Letakkan konfigurasi ini di docker-compose.override.yml. Compose memuat file tersebut secara otomatis dan menggabungkannya 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: buildSetelah konfigurasi tersebut diterapkan, docker compose up -d berikutnya akan melakukan build dari source yang Anda miliki, bukan mengambil apa pun yang ditunjuk latest pada hari itu. Pastikan penggabungan telah diterapkan, lalu lakukan build ulang pada tag tersebut.
docker compose config | grep pull_policy
docker compose up -d --buildTerminasi TLS dengan reverse proxy
Container menggunakan HTTP biasa. Sertifikat harus dikelola oleh komponen di depannya. Caddy merupakan pilihan paling singkat karena dapat meminta dan memperbarui sertifikat dari Let's Encrypt secara otomatis.
gym.example.com {
reverse_proxy 127.0.0.1:8080
}nginx, Traefik, dan Nginx Proxy Manager bekerja dengan cara yang sama. Cloudflare Tunnel juga demikian. Dokumentasi proyek menjelaskan penggunaannya, dan layanan ini tidak memerlukan port masuk yang terbuka sama sekali.
curl -sI https://gym.example.com | head -1Perintah tersebut harus mengembalikan HTTP/2 200 tanpa peringatan sertifikat. Sekarang buka situs dalam browser, lalu pilih Create profile. Jika prompt 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. Sebuah docker compose restart tidak memuat ulang .env.
Jauhkan port 8080 dari Internet publik
Secara default, layanan web memublikasikan 8080 pada setiap interface. 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. Trafik tersebut kemudian diproses dalam chain FORWARD, tempat aturan milik Docker sendiri menerimanya, sedangkan 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}". Nilai yang Anda tetapkan dalam WEB_PORT akan menggantikan bagian kiri pemetaan tersebut, dan sintaks singkat Docker menerima pasangan ip:port di sana. Karena itu, WEB_PORT=127.0.0.1:8080 dapat digunakan.
docker compose config
sudo ss -ltnp | grep 8080Dalam konfigurasi gabungan, di bawah bagian 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 mesin 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. Sekarang Settings menampilkan dasbor Admin untuk membuat dan mencabut kode undangan, sehingga orang yang berlatih bersama Anda dapat mendaftar dan tidak ada orang lain yang dapat melakukannya. openGym tidak mengetahui apa pun tentang identity provider eksternal, sehingga kode undangan tersebut hanya berlaku untuk aplikasi ini dan tidak berlaku untuk hal lain di server; jika Anda lebih memilih memberikan satu akun per orang untuk semua layanan yang Anda jalankan, pasang Authentik di depan sebagai forward auth proxy untuk membatasi akses ke hostname sebelum login passkey milik openGym dimuat.
Lokasi data dan backup 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 ketika API mungkin sedang menulis ke salah satunya. File JSON yang tersalin sebagian akan dipulihkan sebagai file JSON yang rusak. Proses stop dan start memerlukan waktu sekitar dua detik. Selanjutnya, salin archive ke luar server karena archive yang hanya berada di VPS tidak akan bertahan jika VPS mengalami kerusakan. Jangan sertakan media/ dalam backup: ukurannya 140 MB dan berisi image latihan yang dapat diunduh ulang oleh media job secara gratis.
Pemulihan berarti mengekstrak tar 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. Disiplin yang sama berlaku untuk semua layanan lain yang Anda jalankan, dan backup dan upgrade stack Docker Compose membahas prosedur umumnya.
Server MCP bersifat read-only dan berjalan di mesin Anda
MCP (model context protocol) adalah cara client seperti Claude Desktop atau Cursor berkomunikasi dengan server tool lokal. openGym menyertakan server tersebut di mcp/. Server ini bukan bagian dari file compose, bukan container, dan tidak mendengarkan pada port apa pun. Client menjalankannya sebagai proses anak dan berkomunikasi melalui stdio. Karena itu, README menyatakan bahwa server ini tidak pernah meninggalkan mesin Anda.
Instal server tersebut di mesin 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 satu pengguna, karena server dapat mendeteksi satu-satunya profile yang ditemukan. Pada branch default saat ini, server menyediakan sembilan tool: list_routines, get_routine, preview_session, get_week_plan, list_workouts, get_workout, get_bodyweight, estimate_1rm, dan muscle_balance. preview_session menampilkan hal yang benar-benar akan ditampilkan aplikasi di layar saat Anda memulai routine, setelah kebijakan progression dan riwayat Anda mengubah plan. Tag v1.2.7 hanya menyertakan delapan tool lainnya, sehingga server yang dijalankan dari checkout yang dikunci versinya tidak memiliki preview_session. Semua tool tersebut hanya membaca data. Tidak ada yang menulis data. Karena itu, assistant dapat menjawab apa yang Anda bench minggu lalu, tetapi tidak dapat mencatat set, mengedit routine, atau menghapus apa pun. Daftar ini merupakan contoh ringkas dari satu prinsip yang terus muncul dalam desain agent: tool yang Anda expose menentukan seluruh hal yang dapat dilakukan model. Mempelajari cara kerja agent dengan menulis loop sendiri adalah cara tercepat untuk melihat mengapa kumpulan tool read-only merupakan pilihan desain, bukan keterbatasan.
Berikut bagian yang harus ditangani oleh pengguna VPS. OPENGYM_DATA adalah path filesystem, sedangkan data Anda berada di VPS dan client AI Anda berada di laptop. Ada dua opsi yang menjelaskan kondisi ini secara langsung.
- Salin data ke laptop dan arahkan server ke salinan tersebut:
rsync -a --delete user@gym.example.com:/opt/opengym/data/ ~/opengym-data/, lalu setOPENGYM_DATAke~/opengym-data. Server hanya membaca data, sehingga penyalinan tidak menyebabkan kehilangan data. Jalankan kembali rsync saat Anda memerlukan data terbaru. - Jalankan server melalui ssh, dengan
commanddiset kesshdanargsdiset ke["-T", "user@gym.example.com", "OPENGYM_DATA=/opt/opengym/data node /opt/opengym/mcp/src/index.js"]. Node harus terinstal di VPS. Login juga tidak boleh mencetak apa pun ke stdout, karena stdout digunakan sebagai channel protokol.
Kedua opsi tersebut mengasumsikan agent berjalan di laptop Anda. Jika Anda ingin agent berjalan pada mesin yang sama dengan data, OneCLI menyediakan agent dengan sandbox untuk setiap pengguna di 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 menggunakan sudo, atau ubah ownership file pada host. Untuk server yang memang ditujukan untuk 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. Stack compose-nya menjalankan gunicorn untuk 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 pilihan, serta fitur bagi trainer untuk mengelola program orang lain.
openGym terdiri atas dua container, satu folder berisi file JSON, dan tidak memiliki akun yang perlu dikelola selain passkey. Hanya itu perbedaannya. Jika Anda pernah memelihara instalasi Chatwoot, ketika backup berarti dump Postgres bersama direktori upload dan setiap pembaruan versi menjalankan migrasi database, Anda sudah memahami tuntutan pemeliharaan wger.
Jalankan wger jika Anda ingin melacak makanan selain latihan, atau jika Anda memerlukan API untuk dikembangkan. Jalankan openGym jika Anda menginginkan stack yang cukup kecil untuk dibaca secara menyeluruh dalam satu sore dan login tanpa password yang dapat bocor. Konsekuensi pilihan ini adalah tingkat kematangannya: per 19 August 2026, rilis pertama openGym baru berusia satu bulan, sedangkan wger telah memiliki rilis selama bertahun-tahun. Tetapkan versi yang digunakan, simpan backup, dan baca catatan rilis sebelum setiap pembaruan.
Jika Anda masih menentukan layanan apa yang layak menggunakan ruang di server, apa yang layak di-self-host pada 2026 membahas berbagai trade-off, dan aplikasi ini dapat berjalan berdampingan dengan nyaman bersama 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. Tempatkan openGym di balik reverse proxy yang menggunakan sertifikat valid untuk hostname valid, tetapkan RP_ID=gym.example.com dan ORIGIN=https://gym.example.com di .env, lalu jalankan docker compose up -d agar container memuat 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 di sebelah file compose dan 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 latihan dan berat badan, secret untuk kunci cookie sesi, serta vapid.json untuk kunci notifikasi push. Cadangkan dengan docker compose stop api, lalu tar czf ~/opengym-$(date +%F).tar.gz data/, kemudian docker compose start api, dan salin arsip tersebut ke luar server. Jangan sertakan media/, yang berukuran 140 MB dan berisi gambar latihan yang akan diunduh ulang oleh media job secara otomatis.
Apakah Claude dapat membaca riwayat latihan openGym saya?
Ya, melalui server MCP opsional di direktori mcp/, dan hanya untuk membaca data. Pada branch default saat ini, server tersebut menyediakan sembilan tool (delapan pada v1.2.7) yang mencakup rutinitas, pratinjau latihan yang akan dijadwalkan pada sesi berikutnya, rencana mingguan, latihan yang dicatat, berat badan, perkiraan one-rep max, dan keseimbangan otot. Tidak ada tool yang menulis kembali data. Server ini bukan container dan tidak membuka port. Client menjalankannya melalui stdio, lalu server membaca file JSON secara langsung dari OPENGYM_DATA. Karena nilai tersebut 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-host openGym atau wger?
Pilih wger jika Anda ingin melacak makanan dan nutrisi selain log latihan, atau membutuhkan REST API yang terdokumentasi untuk dikembangkan. 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 git tag dan cadangkan data/ sebelum setiap pembaruan.