Tunlease
Debug a webhook on your laptop using its real, unchangeable callback URL — no redeploy, no new URL.
Claim one path on an existing fixed endpoint; its live traffic reaches your
laptop while every other path keeps serving the real app. Ctrl+C to release.
tunle claim /webhooks/stripe/* --to 8080 --gateway staging.myapp.com

Similar in spirit to ngrok,
localtunnel, and
bore, Tunlease solves a different problem:
it keeps the callback URL already in use and temporarily redirects only the
path you claim.
Developer quick start · Platform setup · Architecture · Troubleshooting
English · 繁體中文
How it works
flowchart LR
TP["Third party<br/>(e.g. Stripe)"] -->|"calls the fixed URL"| GW[tunlease gateway]
subgraph Shared["Shared environment"]
GW[tunlease gateway]
App[Original app]
GW -->|"every other path<br/>(fail-open)"| App
end
subgraph Developer["Developer machine"]
CLI[tunle CLI]
Local[Your local service]
CLI -->|"reverse tunnel"| Local
end
GW -->|"claimed path"| CLI
classDef tunlease fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px;
class GW,CLI tunlease;
The gateway receives the fixed host's traffic and forks by path: a claimed
path with a connected tunnel reaches your laptop; every other path is proxied
to the configured original app. Blue nodes are Tunlease's; the rest already
exist.
The safety model combines a path allowlist, exclusive connected tunnels,
optional token authentication, audit logs, and origin fallback. Fallback
covers requests that have no matching connected session before dispatch.
Gateway, Ingress, and origin outages require separate infrastructure or bypass
planning. See the routing and failure contract.
Quick start for developers
Once your platform team gives you the gateway host, an allowed path, and an
optional token, install the CLI:
brew install iml885203/tap/tunlease
Or install the latest verified binary:
# macOS and Linux
curl -fsSL https://raw.githubusercontent.com/iml885203/tunlease/main/scripts/install.sh | bash
# Windows PowerShell (amd64)
irm https://raw.githubusercontent.com/iml885203/tunlease/main/scripts/install.ps1 | iex
Then claim the callback path:
tunle claim /webhooks/provider/callback/* --to 8080 --gateway myapp.example.com
Ctrl+C releases it. HTTPS is the default; use an explicit http:// only for a
gateway without TLS, such as localhost. If the gateway is not set up yet, see the
platform deployment guide.
Claims receive real staging callbacks, including their data and credentials.
Start your local service first, claim the narrowest path you need, and make
callback handling idempotent: provider retries and mid-request tunnel failures
can produce duplicate delivery.
See the developer guide for configuration, lifecycle
commands, and troubleshooting. The installers verify the published SHA-256
checksum before replacing the binary.
Components and deployment model
It is all one tunle binary; a subcommand selects the role:
| Command |
Runs on |
Responsibility |
tunle claim (also list / release) |
Developer machine |
Connect a path to a local service |
tunle gateway |
In front of the app |
Own active paths, terminate tunnels, route requests, and proxy to the original app |
The gateway sits in front of the app and does everything on the server side. It
serves its control plane under the fixed /_tunlease prefix; every
other path is third-party traffic — tunnelled to the developer when a claim
matches, otherwise proxied to the required fail_open_url (the original app).
There is no separate sidecar process.
- Any host, one app origin — run
tunle gateway with fail_open_url pointed at the
app's Service and deploy it in front of the app (Ingress → gateway). This is
the model the Helm chart deploys. Kubernetes is optional.
The gateway does not call the Kubernetes API, so Kubernetes is not required — it
is just the recommended target for the platform model.
Embedding the tunnel client
Go applications can embed the same connected-path and reconnect engine used by the standalone CLI. Their users do not need the tunle binary.
go get github.com/iml885203/tunlease/pkg/tunnelclient@latest
See Embedding the Go client for authentication, a complete lifecycle example, API behavior, errors, upgrades, and integration testing.
Local development
Go and Docker Compose are required. Helm and kubectl are only needed to validate deployment manifests.
Enable the formatting/vet pre-commit hook once per clone:
git config core.hooksPath .githooks
make build # Build the tunle binary into bin/
make test # Run the Go test suite
make lint # Run the pinned golangci-lint container
make preflight # Build, vet, race-test, lint, and reject formatting drift
make e2e # gateway + origin app + local app + real CLI
Documentation
Choose the shortest path for your role:
- Developer receiving callbacks: Developer guide — installation, configuration, CLI usage, and troubleshooting (繁中)
- Platform and service owners: Platform deployment guide — whole-host routing, required origin, Helm, rollout, and security (繁中)
- Contributor understanding the system: Architecture — control/data planes, routing, lifecycle, and recovery (繁中)
- Go application author: Embedding the Go client — module setup, lifecycle API, errors, and testing (繁中)
Status
The gateway, CLI, and reusable Go client are functional. The supported
deployment is deliberately one gateway replica: active paths and their
WebSocket sessions live in the same process. A restart disconnects them; active
clients reconnect and register their paths again when the gateway returns.
Contributing
Contributions are welcome — see CONTRIBUTING.md for the local
setup and the make preflight quality gate. Please report security issues
privately per SECURITY.md. Participation is governed by the
Code of Conduct.
License
MIT