Example manifests for a small cluster, contributed from a K3s setup with the Gateway API and
cert-manager. Read SELF_HOSTING.md first: the passkey requirement (HTTPS,
RP_ID, ORIGIN), the settings in .env.example and the backup advice apply here unchanged.
openGym is pretty simple — a frontend and an API backend. The API keeps everything in plain JSON files on a volume. The Docker Compose file also has a third container that downloads the exercise media once; here that is an initContainer. There are only a few resources to create:
- 2 PVCs:
opengym-data(users, passkeys, workouts, uploads — back this one up) andopengym-media(the exercise images, downloaded again if lost). - 1 Deployment running the API and the web container in a single pod, for simplicity. It stays at
one replica with the
Recreatestrategy: the API's data is files on aReadWriteOncevolume, and two API processes must never write them at once. - An HTTPRoute (the example uses the Gateway API; an Ingress to the
opengymService on port 80 works as well), plus a cert-manager Certificate for the hostname.
git clone https://github.com/DuarteSantos8/openGym # or https://gitlab.com/DuarteSantos8/opengym — same repo
cd openGym
# In kubernetes/deployment.yaml, set RP_ID and ORIGIN in spec.template.spec.containers[api].env
# to your hostname, and the hostname in kubernetes/httproute.yaml.
kubectl apply -k kubernetes/Notes:
- The manifests create and use the
fitnessnamespace (kubernetes/namespace.yaml, set on every resource bykubernetes/kustomization.yaml; rename it in both), and a Gateway calledeginenvoy-gateway-systemwith anhttpslistener; change both to match your cluster. The Gateway has to terminate TLS; this was tested with Envoy Gateway and cert-manager. - The images are the published
ghcr.io/duartesantos8/opengym-apiandopengym-web. The AI Coach with an API key works on that same API image; the Claude and Codex sign-in providers need thecoachbuild target, which is not published — build it yourself (see AI_COACH.md). - The images are pinned to a release (
1.3.9), the API and the web image always to the same one. To update, read the release notes, set the new version on both and apply again; pinning tolatestinstead means a restarted pod can come back on a version you never chose. - Settings are environment variables on the
apicontainer, named as in.env.example. Keep secrets such as the push keys in a Kubernetes Secret and load them withenvFrom. - Client addresses. The web container overwrites
X-Forwarded-Forwith the address it was reached from (seeweb/nginx.conf.template), and behind a Gateway that is the gateway's pod, not the visitor. So the sign-in throttle, which counts attempts per address, acts on the whole instance at once, and the activity log records the gateway's address.TRUST_PROXYis left off because it would not change that: the API would read the same gateway address from the header — and any pod that reaches port 3000 directly could put its own address there. Getting real visitor addresses needs the gateway to preserve them (for exampleexternalTrafficPolicy: Localon its LoadBalancer Service) and to set a header of its own that overwrites whatever a client sent; pass that through withCF_CONNECTING_IPon thewebcontainer (as.env.exampledescribes for Cloudflare), turn onTRUST_PROXY=1on theapicontainer, and add a NetworkPolicy so only the web container's pod reaches port 3000.