28 min read

Set Up openGym with Passkeys and Localtonet

Install and verify openGym with Docker Compose, configure domain-based passkeys, then enable secure remote browser access with Localtonet over HTTP.

A remote phone connects through an HTTP tunnel to openGym running locally in Docker, with passkey authentication.
openGym runs locally in Docker while Localtonet carries remote browser traffic to it.
Self-Hosting ยท openGym ยท Localtonet ยท 2026

Run your workout tracker on your own server, enable domain-based passkeys, and reach it securely from your devices

openGym is a self-hosted gym and body-weight tracker designed to synchronize routines, workouts, progress, and profiles across phones and computers. This guide installs the project with Docker Compose, verifies the local service at http://localhost:8080, explains the domain and HTTPS requirements behind passkey authentication, and then connects the working application to an HTTP tunnel with Localtonet. We keep installation and troubleshooting separate from remote access so that you can confirm each layer before exposing anything publicly.

๐Ÿ”’ Passkey authentication requires HTTPS and a correctly configured domain ๐ŸŒ Local service defaults to port 8080 โšก Docker Compose uses published prebuilt images for amd64 and arm64

How an openGym and Localtonet deployment works

Remote browsers reach a Docker-hosted openGym service through a Localtonet HTTP tunnel.
The tunnel forwards requests from a public domain to the openGym service on the private host.

openGym is a self-hosted web application for planning routines, running guided workouts, recording sets and body weight, and reviewing training progress. Its self-hosted edition keeps application data on infrastructure you control while making the same profile available from multiple devices. The application includes passkey-based profile sign-in, optional password authentication that can be enabled per instance, device pairing through a one-time code or QR code, and JSON data export.

The documented quick-start deployment uses Docker with the Docker Compose plugin. The repository contains a Compose configuration and an example environment file. Installation consists of cloning the repository, copying .env.example to .env, pulling the published container images, and starting the stack. Once the containers are running, the default browser endpoint is http://localhost:8080. The documented default web port is controlled by WEB_PORT=8080.

Local access and remote access are separate layers. Docker Compose starts openGym and publishes its web service on the host. Localtonet then connects that already working HTTP endpoint to a public HTTPS address. Our client establishes an outbound connection to a Localtonet relay, so this workflow does not require inbound router port forwarding, a public IP address, firewall changes, or a separate VPN.

๐Ÿ‹๏ธ Self-hosted workout data openGym stores the self-hosted application's data on infrastructure under your control and synchronizes profiles across devices.
๐Ÿณ Docker Compose deployment The official quick start uses a repository-provided Compose file and published amd64 and arm64 container images.
๐Ÿ”‘ Passkey profile access Passkeys can use device authentication such as Face ID, Touch ID, or fingerprint verification, subject to browser and device support.
๐Ÿ”’ HTTPS public origin Remote passkey use requires an HTTPS domain and matching openGym relying-party and origin configuration.
๐ŸŒ Outbound tunnel connection The Localtonet client connects outward to our relay and provides a public URL for the local HTTP service.
๐Ÿ“ฑ Cross-device browser access Once the application and authentication origin are configured, phones and computers can reach the same self-hosted instance.

Understand the three addresses involved

It helps to distinguish the addresses used at each layer. Inside the Docker deployment, the web container participates in the Compose network. On the Docker host, the documented default endpoint is http://localhost:8080. After an HTTP tunnel starts, users receive a public HTTPS URL from Localtonet. Passkey configuration must describe the public HTTPS identity that browsers will actually use, not merely the local development address.

Layer Address or identity Purpose
Docker service Managed by the repository's Compose configuration Connects the openGym application components inside the Docker environment.
Docker host http://localhost:8080 by default Provides the first endpoint to test before remote access is configured.
Public browser origin The HTTPS URL assigned or configured for the HTTP tunnel Allows remote browsers to reach openGym and supplies the origin used for domain-based passkeys.
Install first, expose second

Do not troubleshoot Docker, openGym, passkeys, and tunneling at the same time. First prove that http://localhost:8080 works directly on the host. Then configure the intended HTTPS hostname in openGym, restart the application, and finally start the Localtonet HTTP tunnel.

Prerequisites

Use a machine that can remain powered on while you want openGym to be available. The public tunnel is available only while the selected Localtonet client is connected and the tunnel is running. The openGym containers must also remain running.

Docker and Docker Compose

The official openGym quick start requires Docker with Compose. Verify both before cloning the repository:

docker --version
docker compose version

Both commands should print version information. If docker works but docker compose does not, install or enable the current Docker Compose plugin for your operating system. This guide uses the modern space-separated docker compose form, not the older standalone docker-compose command.

Your user must be permitted to communicate with the Docker daemon. The exact setup differs by operating system and Docker installation method. If Docker reports a permission error, correct the local Docker permissions rather than weakening openGym's configuration or running unrelated commands as an administrator.

Git and local storage

Git is needed to clone the openGym repository. You also need enough local storage for the repository, container images, application data, and future backups. The supplied evidence does not establish a fixed minimum disk requirement because actual use depends on retained data and any uploaded media.

git --version

The openGym images include the exercise animations, so the quick start does not require a separate animation download after the containers are pulled.

A hostname plan for passkeys

Local testing can begin at http://localhost:8080, but remote passkey use requires HTTPS on a domain. Before registering the passkeys that you intend to use remotely, decide which public hostname will identify this openGym instance. HTTP and File Server tunnels with Localtonet can use Random Sub Domain, Custom Sub Domain, or Custom Domain process types. All three serve content from a public HTTPS address.

A stable hostname is especially important for passkeys because WebAuthn credentials are scoped to a relying party and validated against an expected web origin. If the public hostname later changes, a credential registered for the old relying party may not authenticate at the new one. Choose the process type that fits your required hostname stability and is available for your account and current dashboard configuration.

Do not publish an uninitialized instance casually

Complete local verification and establish the intended account or profile controls before sharing the public URL. A tunnel makes the web endpoint reachable from the internet. It does not replace openGym authentication, host security, container maintenance, backups, or least-privilege access practices.

Install openGym with Docker Compose

Run the following installation from the machine that will host the application. These commands follow openGym's documented quick-start sequence. Do not place secrets directly in shell history, and do not paste the contents of the resulting .env file into tickets or public discussions.

1

Clone the openGym repository

Download the project's Compose configuration, example environment file, and supporting repository content from the official repository.

git clone https://github.com/DuarteSantos8/openGym
2

Enter the repository directory

Run the remaining Compose commands from the cloned openGym directory so Docker can find the included Compose file.

cd openGym
3

Create the local environment file

Copy the supplied example rather than constructing an environment file from memory. This preserves the variable names and defaults expected by the current repository version.

cp .env.example .env
4

Review the environment before startup

Open .env in a local text editor. The documented default is WEB_PORT=8080. Keep that value for the first verification unless port 8080 is already occupied. Avoid changing unrelated values until the default deployment has been tested.

5

Pull the published container images

Pull the prebuilt images referenced by the repository's Compose configuration. The project publishes images for amd64 and arm64. The project notes that skipping this pull causes the deployment to build from source instead.

docker compose pull
6

Start the stack in the background

Start the configured services in detached mode. Docker Compose will create the required local network and start the containers defined by the repository.

docker compose up -d

The repository's Compose file pulls the project's API and web images. The project documents published images from the same release tag in GitHub Container Registry, with a GitLab registry alternative available if an operator deliberately changes the image references. There is no reason to alter those image lines for the normal quick-start installation.

Confirm the containers started

Use Docker Compose to inspect the services associated with the current project:

docker compose ps

Look for services that are running rather than repeatedly restarting or exiting. The exact service names come from the current repository's Compose file and may change between releases, so this guide does not hardcode them. If a service is not healthy or running, inspect the stack's logs:

docker compose logs

To follow new log entries while reproducing a problem, use:

docker compose logs -f

Stop the live log display with Ctrl+C. This interrupts log viewing only. It does not stop containers that were started in detached mode.

Verify openGym locally before remote access

Open a browser on the Docker host and visit:

http://localhost:8080

With the documented default configuration, the openGym interface should load. Select Create profile to begin using the application. The local test demonstrates that the containers started, the web port was published, and a browser on the host can reach the application.

Do not treat a successful container status as a complete application test. A container can be running while the page remains unavailable because of a port conflict, startup error, or application dependency problem. Conversely, a remote tunnel cannot repair a local endpoint that is already failing.

Test from another device on the local network

If you intend to run the Localtonet client on a different machine from the Docker host, first prove that the client machine can reach openGym over the local network. Use the Docker host's LAN address with the configured web port. The exact LAN address depends on your network, so it must be obtained from the host or router rather than guessed.

If the page works on the Docker host but not from another LAN device, investigate host firewall rules, Docker's published-port binding, client isolation on the Wi-Fi network, and whether the correct host address is being used. Do not open an internet-facing router port for this workflow. Localtonet makes an outbound relay connection and does not require inbound router port forwarding.

Verify the effective port

This guide uses port 8080 because WEB_PORT=8080 is the documented default. If you changed WEB_PORT, substitute that value in the local browser address and later in the Localtonet tunnel target. The tunnel must point to the port that actually serves openGym.

Use one known-good local target

Record the exact local address and port that work from the machine running our client. When both applications run on the same host, the documented openGym endpoint is http://localhost:8080. When they run on different devices, the Localtonet client needs a local network address that it can actually reach.

Configure openGym for domain-based passkeys

The openGym configured domain, browser hostname, and passkey identity must match.
Passkey registration and sign-in depend on using the domain configured for openGym.

Passkeys are based on WebAuthn. A browser does not consider a passkey to be a free-floating password that can be submitted to any copy of a login page. Registration associates the credential with a relying-party identity, and authentication checks the page's origin. This anti-phishing design is why changing a self-hosted application's hostname without updating its configuration causes passkey registration or sign-in failures.

openGym documents that using passkeys from a phone requires HTTPS on a domain and a two-line change in .env. The relevant settings are RP_ID and ORIGIN. Configure them for the public HTTPS address through which users will access this particular instance.

Setting Represents Configuration principle
RP_ID The WebAuthn relying-party identifier It must correspond to the domain identity used for passkey registration and authentication.
ORIGIN The browser origin allowed by the application It must correspond to the public HTTPS origin used in the browser.
WEB_PORT The host port for openGym's web service Keep the default 8080 unless there is a concrete local reason to change it.

Preserve the exact variable syntax supplied by the version of .env.example that you cloned. Enter the public hostname and origin according to the comments and examples in that file. The project-specific evidence supplied for this guide establishes the variable names and domain requirement, but it does not include the complete current example values or every accepted hostname pattern. For that reason, we do not invent a sample domain or add unsupported formatting.

After saving .env, recreate the Compose services so that the containers receive the updated environment:

docker compose up -d

Then inspect container status and logs again:

docker compose ps
docker compose logs

Register passkeys from the final public origin

Register the passkey while using the same public HTTPS hostname that you expect to use later. A passkey registered under localhost or an earlier hostname should not be assumed to work under a different public domain. If you change the hostname after registration, update the openGym configuration and be prepared to register a credential appropriate to the new relying-party identity.

openGym supports profiles on multiple devices and documents pairing new devices by a one-time code or QR code. Follow the in-application pairing flow rather than sharing profile data or credentials manually. Password sign-in can also be enabled per instance, but this guide does not provide an environment variable for that option because the supplied evidence does not establish its current configuration name or default.

Origin matching must be exact

A page loading successfully does not prove that WebAuthn is correctly configured. If profile creation works but passkey registration or sign-in fails, compare the browser's HTTPS hostname with openGym's configured RP_ID and ORIGIN. Also check whether an old passkey was registered for a different hostname.

Connect openGym to an HTTP tunnel with Localtonet

Begin this section only after openGym loads through the local target. Our HTTP tunnel forwards public browser requests to that local web endpoint. It does not install openGym, start its containers, or alter its environment configuration.

HTTP tunnels can use Random Sub Domain, Custom Sub Domain, or Custom Domain as their process type. Each option serves the same local content at a public HTTPS address. For passkeys, select a public naming approach that you can keep aligned with the values in openGym's RP_ID and ORIGIN. Availability can vary by account, plan, current dashboard options, or deployment, so use the options shown in your dashboard rather than assuming that every choice is available.

1

Install and run the Localtonet client

Install our client on the Docker host or on another device that can reach the verified openGym address. Keep the client running whenever remote access is required.

2

Authenticate or select the client device

Use the device-specific authentication token associated with the client that will run the tunnel. Treat the token as a secret. Do not include it in screenshots, commands, documentation, or support messages.

3

Select an available relay server

Choose a currently available relay server or region from the dashboard. Server codes and regional availability can change, so they must be obtained from the current product interface rather than copied from an old tutorial.

4

Create an HTTP tunnel to openGym

Choose the HTTP tunnel family and an appropriate process type. Set the local target to the address that succeeded during verification and use port 8080 unless you changed WEB_PORT. If the Localtonet client runs on another device, use a reachable LAN address instead of that other device's loopback address.

5

Start the tunnel

Creating a tunnel does not make it run. Use the Start button, then wait for the selected client and tunnel to show that they are connected. The tunnel remains available only while the client is connected and the tunnel is running.

6

Open and test the public HTTPS URL

Visit the assigned public address from a separate browser or device. Confirm that openGym loads, authentication behaves as expected, and a passkey can be registered or used from the configured origin. Stop or delete the tunnel when public access is no longer required.

For the current dashboard sequence and field descriptions, consult our Localtonet HTTP tunnel documentation. Custom-domain DNS requirements should always be checked against current documentation before changing public DNS records.

Choosing the correct local target

If openGym and our client run on the same machine, target the local service represented by the verified localhost:8080 endpoint. If our client runs elsewhere on the LAN, localhost would refer to the client machine itself, not the Docker host. In that arrangement, supply the Docker host address that worked during the LAN test.

Avoid pointing the tunnel at a Docker-internal container address discovered through temporary inspection. Such addresses can change when containers are recreated. Use the host-published web port documented by openGym unless you have intentionally designed a different Docker networking arrangement.

Confirm the final hostname before relying on passkeys

Once Localtonet displays the public URL, compare it with the openGym environment configuration. If you initially used a temporary generated hostname and later move to a custom subdomain or custom domain, update RP_ID and ORIGIN, recreate the services, and validate passkeys again. Do not assume that a passkey tied to the old relying party will migrate automatically.

Routine operation, updates, and recovery planning

Start and inspect openGym

From the repository directory, the normal detached startup command is:

docker compose up -d

Check the current state with:

docker compose ps

If the browser is unavailable after a reboot, check Docker itself, the Compose services, and then the Localtonet client and tunnel. These are independent processes. A connected tunnel cannot serve an application whose containers are stopped, and a healthy application is not public while our client or tunnel is offline.

Stop the Compose stack

To stop and remove the running containers and Compose network while retaining persistent data managed by the deployment, use:

docker compose down

Do not add volume-removal options unless you understand exactly which stored data would be deleted. The supplied project evidence does not identify every current volume name or host data path, so this guide intentionally does not prescribe destructive cleanup commands.

Approach updates conservatively

Before updating, read the release notes for the version you plan to deploy and make a verified backup. Containerized applications can include schema migrations or changes to environment variables. The repository's documented installation uses tagged published images, but the exact update procedure and migration requirements can vary by release.

A typical Compose image refresh involves pulling the image references currently selected by the repository and recreating the services:

docker compose pull
docker compose up -d

However, do not treat those two commands as a substitute for version-specific release instructions. If the checked-out Compose file itself has changed, update the repository carefully, review the differences in .env.example, merge required changes into your private .env, and only then recreate the services. Never overwrite a working environment file blindly.

Back up application data and configuration

openGym provides an application-level JSON export, which is useful for retaining a portable copy of training information. Keep regular exports somewhere other than the server. Also protect the server-side persistent data and your reviewed environment configuration according to the storage definitions in the exact Compose file you deployed.

The supplied evidence does not establish a single data directory or one backup command that applies to every openGym version and storage arrangement. Rather than inventing a path, inspect the current docker-compose.yml for named volumes and bind mounts. Back up those persistent locations while following appropriate consistency procedures for the services involved. Test restoration on a separate deployment before considering a backup strategy complete.

Stop public access independently

When you want openGym to remain available only locally, stop its Localtonet tunnel. You do not need to shut down the Docker stack. Conversely, stopping Docker leaves the tunnel configuration present but removes the working local target. You can also delete a tunnel when it is no longer needed.

Security checklist for a remotely accessible workout tracker

Self-hosting gives you operational control, but it also makes you responsible for patching, authentication, backups, and deciding who can reach the service. Workout history, body weight, profile information, and progress photos can be sensitive. Treat the deployment as a private application even if it does not hold financial or workplace data.

๐Ÿ”‘ Protect every identity layer Keep Localtonet device tokens secret, use openGym's profile authentication, and protect the host account and Docker administration separately.
๐ŸŒ Expose only the web service Create an HTTP tunnel to the published openGym web port. Do not expose Docker control sockets, databases, administrative shells, or unrelated host services.
๐Ÿงฉ Keep versions current Review openGym releases, container updates, the host operating system, Docker, browsers, and the Localtonet client as part of routine maintenance.
๐Ÿ’พ Maintain recoverable backups Combine openGym JSON exports with backups of persistent server data, and periodically test that the data can be restored.
๐Ÿ›‘ Limit availability when practical Stop the tunnel when remote access is unnecessary, especially during initialization, maintenance, or authentication changes.
๐Ÿ“‹ Review optional features Features involving provider keys, administrative access, or additional integrations should remain disabled until deliberately configured and secured.

Do not place provider credentials, Localtonet tokens, or private configuration values in the repository. The optional openGym AI coach accepts user-supplied provider configuration and is off by default. If you enable optional integrations, review what they can access and how their credentials are stored before making the application public.

Keep the public hostname deliberate and stable. Unexpected hostname changes are not merely a usability issue when passkeys are involved. They can lead users to register duplicate credentials, encounter authentication failures, or mistake a configuration problem for lost account data.

A public HTTPS URL is not an authorization policy

HTTPS protects browser transport to the public endpoint, but it does not decide who should access your profiles. Keep openGym authentication enabled and correctly configured. Never rely on an obscure URL as the only protection for personal workout data.

Troubleshooting openGym, passkeys, and the tunnel

A five-step troubleshooting flow checks Docker, local access, domain matching, the tunnel, and remote access.
Testing each layer in order isolates container, domain, passkey, and tunnel failures.

The repository cloned, but Docker Compose cannot find a configuration

Confirm that your shell is inside the cloned openGym directory. List the directory contents and verify that the repository's Compose file and .env.example are present. If the clone was interrupted, remove the incomplete copy only after confirming that it contains no work you need, then clone it again.

The docker compose command is unavailable

Verify that Docker and the Compose plugin are installed. A working Docker engine does not necessarily mean that the Compose plugin is present. Install the current plugin using the supported method for your operating system, then rerun docker compose version.

Port 8080 is already in use

Another service may already be listening on the default port. Identify that service and decide whether it should be stopped or whether openGym should use a different WEB_PORT. If you change the port, recreate the Compose services and use the new value for both local testing and the Localtonet target.

Avoid changing multiple ports or networking values at once. Make one controlled change, start the stack, inspect logs, and verify the resulting browser endpoint.

The containers exit or restart repeatedly

Run docker compose ps and docker compose logs. Look for an invalid environment value, image pull failure, unavailable dependency, file permission problem, or resource exhaustion. Keep the copied example file as the configuration baseline and undo speculative edits.

If image pulling fails, verify the host's DNS resolution, outbound internet access, available storage, and registry access. The normal quick start uses prebuilt images. Building from source introduces additional variables and should not be the first workaround for a registry or connectivity problem.

openGym works on the host but not from the Localtonet client device

This usually indicates a local reachability problem. From the device running our client, test the Docker host's LAN address and selected web port. Confirm that both devices are on a network that permits peer communication and that the Docker host's firewall allows the intended LAN connection.

Remember that localhost always means the machine making the connection. If our client runs on a separate device, targeting localhost:8080 asks that separate device for a service on its own port 8080.

The public URL does not load

Check the layers in order:

  1. Confirm that openGym still loads from the local target.
  2. Confirm that the Docker containers are running.
  3. Confirm that the Localtonet client is connected.
  4. Confirm that the correct device token is associated with the tunnel.
  5. Confirm that the tunnel has been started, not merely created.
  6. Confirm that its local IP address and port match the tested endpoint.

If the tunnel worked and then stopped, investigate whether the host slept, the Localtonet client exited, the Docker stack stopped, the local address changed, or the tunnel was manually stopped.

The page loads publicly, but passkey registration fails

This narrows the problem to application or WebAuthn configuration rather than basic tunneling. Check that the browser uses HTTPS, that the hostname is the intended one, and that RP_ID and ORIGIN describe that public identity. Recreate the Compose services after changing .env, then inspect logs for configuration errors.

Also consider whether the device and browser support the passkey flow being attempted. Browser privacy settings, device authentication availability, and credential synchronization behavior vary by platform. These client-side capabilities are separate from the tunnel.

A passkey worked before the public hostname changed

The credential may be scoped to the former relying party. Correct openGym's configuration for the final hostname and use the application's supported profile or device-pairing workflow to establish access from the new origin. Do not delete the only working credential or profile access path until the replacement has been tested.

The public application displays stale behavior after an update

Confirm which images are running, recreate the Compose services, and try a private browser window to rule out cached frontend resources. If the update introduced environment settings, compare the current .env.example with your private .env. Preserve existing secrets and deliberate overrides while adding only required new values.

Workout data appears missing

Stop making destructive changes. Verify that you are opening the intended instance and profile, that the same persistent storage is mounted, and that the update did not start a fresh deployment from a different directory or Compose project. Review Docker volume and bind-mount definitions before removing or recreating anything. Restore only from a tested backup after understanding what changed.

Frequently asked questions

What port does openGym use by default?

The documented default is WEB_PORT=8080, and the local quick-start endpoint is http://localhost:8080. If you change WEB_PORT, use the new port for local verification and for the Localtonet HTTP tunnel target.

Does openGym require Docker Compose?

Docker with Compose is the project's documented quick-start installation path covered by this guide. The repository also contains material for other deployment approaches, but their prerequisites and workflows are outside this Docker Compose tutorial.

Can I test openGym without configuring a public domain?

Yes. The documented local endpoint is http://localhost:8080, where you can load the application and create a profile. Remote passkey use is a separate requirement and needs HTTPS on a domain with matching RP_ID and ORIGIN settings.

Why do passkeys need RP_ID and ORIGIN?

Passkeys use WebAuthn's domain-bound security model. RP_ID identifies the relying party, while ORIGIN identifies the HTTPS browser origin expected by the application. These checks help prevent a credential registered for one site from being replayed by an unrelated site.

Can I use a generated Localtonet subdomain with openGym passkeys?

The public HTTPS hostname must match openGym's passkey configuration and remain available when you authenticate. If you use a generated hostname, configure openGym for that identity and understand the effect of changing it later. For long-term passkey use, choose the most stable naming option available for your account and workflow.

Do I need to forward port 8080 on my router?

No. The Localtonet client establishes an outbound connection to our relay server. This workflow does not require inbound router port forwarding, a public IP address, firewall changes, or VPN setup.

Does creating a Localtonet tunnel start it automatically?

No. Creating the tunnel saves its configuration, but you must use the Start button. The selected client must also remain connected. You can later stop or delete the tunnel independently of the openGym containers.

Can the Localtonet client run on a different device from openGym?

Yes, provided that the client device can reach the openGym host's local IP address and web port. In that arrangement, do not use localhost as the target because it would refer to the Localtonet client device rather than the Docker host.

How should I back up openGym?

Use openGym's JSON export for a portable copy of application data, and also back up the persistent server storage defined by the Compose version you deployed. The available evidence does not establish one universal host path, so inspect your current Compose file rather than copying an unverified backup command. Store backups away from the server and test restoration.

Is the public URL available if my server is turned off?

No. Remote access depends on the openGym containers, the host, the Localtonet client, and the tunnel all being operational. The tunnel is available only while the selected client is connected and the tunnel is running.

Connect your verified openGym instance with Localtonet

Install openGym locally, confirm the service on port 8080, configure its final HTTPS passkey origin, and then create an HTTP tunnel for secure remote browser access without inbound router port forwarding.

Get Started Free โ†’

Localtonet is a secure multi-protocol tunneling and proxy platform designed to expose localhost, devices, private services, and AI agents to the public internet supporting HTTP/HTTPS tunnels, TCP/UDP forwarding, mobile proxy infrastructure, file server publishing, latency-optimized game connectivity, and developer-ready AI agent endpoint exposure from a single unified control plane.

support