Use cases
The same two programs — a server on a public host and small agents that dial out to it — cover a surprising range of jobs. Find yours here, then follow the link to the how-to.
Show a work in progress
You built something and want to show it now — to a client, a colleague, the community. Run the agent on your laptop with the one-line command from the panel, share demo.tunnels.example.com, and take feedback while the code is still warm.
- Nothing to install on the visitors' side and nothing to forward on yours.
- Protect the demo with a quick Internal user if "anyone with the link" is too public — see Authentication.
- Done demoing? Delete the token; the address stops working immediately.
A home service, published properly
The classic: a self-hosted app on the home server, and a home line that is behind NAT or carrier-grade NAT. The agent runs next to the app (or anywhere in your LAN, pointing at 192.168.1.50:8123), your router stays closed, and visitors get a normal HTTPS address on your domain.
This is the walkthrough Getting started follows end to end — DNS, server, first tunnel, and keeping the agent alive with systemd. For "family and friends only" setups, put the users in a group and bind the group to the tunnel.
Docker Compose: the portable setup
Wrap the agent next to your app and the whole thing becomes one file you can drop on any host — the app side publishes zero ports, the only public endpoint is your server.
services:
app:
image: myapp:latest
agent:
image: ghcr.io/reddec/tunnel-me
command: connect -u https://tunnels.example.com app:3000
environment:
TUNNEL_TOKEN: <token>
restart: unless-stopped
The agent reaches the app by its Compose service name (app:3000) — no port mappings anywhere. Move the stack to another machine, another LAN, another country: reconnecting is automatic, the public address never changes.
Kubernetes: scale without an ingress
Run the official image as a sidecar container next to your app. Containers in a pod share their network, so the agent reaches the app on localhost. Scale the Deployment — every replica dials out with the same token, and the server alternates visitor requests across all live connections.
# pod spec, excerpt
containers:
- name: app
image: registry.example.com/myapp:1.0
- name: tunnel
image: ghcr.io/reddec/tunnel-me
args: ["connect", "-u", "https://tunnels.example.com", "localhost:8080"]
env:
- name: TUNNEL_TOKEN
valueFrom:
secretKeyRef:
name: tunnel-token
key: token
flowchart LR
v(["Visitors"]) --> srv["tunnel-me server<br/>public host, one port"]
subgraph k8s["Kubernetes cluster — no ingress, no public ports"]
direction TB
subgraph p1["Pod 1"]
a1["app :8080"] --- g1["agent sidecar"]
end
subgraph p2["Pod 2"]
a2["app :8080"] --- g2["agent sidecar"]
end
end
srv <-->|"outbound, shared token"| g1
srv <-->|"outbound, shared token"| g2
No Service, no Ingress, no LoadBalancer line on the cloud bill — the cluster never accepts a single inbound connection. Scaling to zero replicas simply leaves the tunnel offline (visitors get an error page until a replica returns). Load distribution is plain round-robin across live connections, not a latency-aware balancer.
Expose something that was never meant to be public
A vendor appliance, an admin UI, a database dashboard — running on an internal network or behind a VPN, reachable from one host only, and policy says it stays that way. Put the agent on the one host that can reach it, point the upstream at host:port, and the service appears on your domain without a single new firewall rule on the internal side.
flowchart LR
v(["Visitor"]) -->|"sign-in required"| srv["tunnel-me server<br/>public host"]
srv <-->|"agent dials out<br/>across NAT / VPN"| ag["Agent<br/>any host that reaches the service"]
ag -->|"host:port"| svc(["Internal service<br/>still never directly exposed"])
The "secure" part is yours to switch on: protect the tunnel with OIDC or Internal sign-in so only approved visitors get past the door, and forward their username to the app in the X-User header if it wants to know who is knocking. If the app is picky about the hostname it is served under, the tunnel's custom domain setting rewrites the Host header it receives — see Admin panel.
A team, one domain, company single sign-on
For a team that keeps standing up internal tools: one server owns dev.example.com, everyone exposes their service as a subdomain, and access is managed in one place instead of six.
- The panel sits behind company SSO (
--auth oidcon the server) — only people your identity provider vouches for can create tunnels at all. - Each tunnel uses OIDC with the same company provider: register the wildcard redirect URI once (
*.dev.example.com/oauth2/callback), then bind per-tunnel groups — the database team's tool only admits the database group. - Visitors authenticate with the accounts they already have; apps receive the username in
X-Userwithout implementing any login themselves.
The mechanics of all three layers live in Authentication.