Self-hosting
Run the whole osmRouter stack on a single box you control with Docker Compose.
The entire platform — relay, control-plane API, Postgres, Redis, and Caddy for TLS — ships as a Docker Compose deployment. Run it on one server and the hosted limits no longer apply; you set your own.
What you'll run
| Service | Role |
|---|---|
relay | Tunnel data plane — agents dial in, visitors are forwarded out. |
api | Control plane — auth, tokens, domains, usage, metering. |
caddy | TLS termination (Let's Encrypt), wildcard certs, serves the web apps. |
postgres | Accounts, tunnels, domains, usage. |
redis | Ephemeral state and coordination. |
Prerequisites
- A server with a public IP (any small cloud VM works).
- A domain you control, with DNS you can edit.
- Docker and Docker Compose installed.
DNS
Point these records at your server's IP — the wildcard is required for random tunnel URLs:
| Record | Type | Points to |
|---|---|---|
@ | A | server IP |
app, api, docs, status | A | server IP |
tunnel | A | server IP |
* | A | server IP (wildcard — required for <name>.yourdomain.com) |
Without the * wildcard record and wildcard TLS, random and pinned tunnel
subdomains can't resolve or get a certificate. This is the one record people
miss.
Bring it up
Get the self-host bundle
The full platform (relay, control plane, Postgres, Redis, Caddy) ships as a Docker Compose bundle, available with the Enterprise / self-host plan. Email contact@osmapi.com to get it, then configure your domain and secrets:
cp .env.example .env
# set OSM_DOMAIN=yourdomain.com and the generated secretsStart the stack
docker compose --env-file .env -f infra/docker-compose.yml up -dThe API applies database migrations on boot.
Verify
Check the services are healthy and the public URLs answer:
docker compose -f infra/docker-compose.yml ps
curl -I https://app.yourdomain.comCaddy issues certificates on first request; if issuance fails, confirm DNS has fully propagated and restart Caddy.
Point the client at your relay
Your users set OSM_RELAY and OSM_DOMAIN to your deployment:
OSM_RELAY=tunnel.yourdomain.com:8443 \
OSM_DOMAIN=yourdomain.com \
OSM_TOKEN=osm_xxxxx \
osmrouter http 8080Operating it
- Logs:
docker compose -f infra/docker-compose.yml logs -f api - Rebuild after an update:
git pull && docker compose -f infra/docker-compose.yml up -d --build - Backups: back up the Postgres volume regularly.
Next steps
- Custom domains — let your users bring their own hostnames.
- Troubleshooting — common deployment gotchas.