Expose β Pure Go Localhost Tunneling CLI


The drop-in, open-source Go alternative to ngrok.
Start instantly on free public relays with zero config and zero signups, or graduate to your own self-hosted relay on a $5 VPS in a single <10MB binary. Zero lock-in.
Install via Homebrew:
brew install kernelshard/tap/expose
Tunnel your local server (e.g. port 3000):
expose tunnel 3000
π‘ What is this for?
When you run an app on http://localhost:3000, only your laptop can access it. Expose creates a temporary public HTTPS URL that connects directly to your local server:
- πͺ Webhook Testing: Give Stripe, GitHub, or Twilio a live HTTPS URL to test webhook callbacks locally.
- π± Mobile & Device Testing: Open your local website on a physical smartphone or tablet over 4G/5G.
- π₯ Instant Previews: Send a live demo link to a friend or client without deploying to cloud servers.
- π Private Self-Hosting: Route traffic through your own private VPS instead of commercial SaaS relays.
π Why Expose?
| Feature |
π Expose |
ngrok |
Cloudflare Tunnel |
LocalTunnel (Node) |
| Zero Signup Required |
β
Instant |
β Mandatory account |
β Mandatory account |
β
Instant |
| Single Static Binary |
β
(<10MB) |
β
(~30MB) |
β
(~50MB) |
β (requires Node.js) |
| Open Source |
β
Apache 2.0 |
β Proprietary |
β
Apache 2.0 |
β
MIT |
| Self-Hostable Relay |
β
expose server |
β SaaS only |
β Cloudflare network |
β οΈ Legacy Node server |
| Language & Runtime |
Pure Go (zero CGO) |
Go / C |
Go daemon |
Node.js (V8 heap) |
| Free Custom Domains |
π‘ Partial (Self-Hosted) |
π² Paid plan |
β
Cloudflare DNS |
β Not supported |
| Memorable Subdomains |
β
Auto-assigned (swift-falcon-4821) |
β Random hex (free tier) |
β Random hex |
β οΈ Limited words |
β‘ Quickstart
Option A: Instant Public Tunnel (Zero Setup)
Expose your local development server in seconds without creating an account:
# Public relay (default)
expose tunnel 3000
# Or via Cloudflare Quick Tunnels
expose tunnel 3000 --provider=cloudflare
Option B: Sovereign Mode (Self-Hosted on Your VPS)
Want permanent custom subdomains, full privacy, and zero rate limits? Run your private relay on any $5 VPS:
# 1. On your VPS (Turnkey Docker + Caddy Auto-TLS)
git clone https://github.com/kernelshard/expose.git && cd expose/deploy
cp .env.example .env && docker compose up -d
# 2. On your laptop
expose tunnel 3000 --server=tunnel.yourdomain.com:7890 --subdomain=api
π¦ Installation Options
1. Homebrew (macOS & Linux β Recommended)
brew install kernelshard/tap/expose
2. One-Line Shell Installer
curl -fsSL https://raw.githubusercontent.com/kernelshard/expose/main/install.sh | sh
Pin a specific version or install to a custom path:
# Pin version
VERSION=v0.6.1 curl -fsSL https://raw.githubusercontent.com/kernelshard/expose/main/install.sh | sh
# Custom directory
BINDIR=~/.local/bin curl -fsSL https://raw.githubusercontent.com/kernelshard/expose/main/install.sh | sh
3. Pre-built Binaries
Download the latest pre-compiled binary for your architecture (macOS, Linux, Windows) from the Releases Page.
4. Via Go Install (Go 1.22+)
go install github.com/kernelshard/expose/cmd/expose@latest
5. From Source
git clone https://github.com/kernelshard/expose.git
cd expose
go build -o expose ./cmd/expose
./expose --version
π Self-Hosted Relay Server
Need complete privacy, permanent custom subdomains, and zero third-party relay dependence? Run your own private tunnel gateway on any $5 VPS (DigitalOcean, Hetzner, AWS Lightsail).
Option 1: Turnkey Docker Compose + Caddy Auto-TLS (Recommended)
The fastest path to production with automated TLS termination and edge reverse-proxying:
git clone https://github.com/kernelshard/expose.git
cd expose/deploy
cp .env.example .env
# Set DOMAIN=tunnel.yourdomain.com in .env
docker compose up -d
See deploy/README.md for DNS setup and configuration options.
Option 2: Standalone Binary
Run the native compiled binary directly on bare metal:
# Start your private relay server
expose server --domain=tunnel.mysite.com --control-port=7890 --public-port=8080
Connect From Your Laptop
expose tunnel 3000 --server=tunnel.mysite.com:7890 --subdomain=my-app
βοΈ Project Configuration (.expose.yml)
Save defaults per project so you can just type expose tunnel:
# Create .expose.yml in current directory
$ expose init
β Config created: .expose.yml
project: expose
port: 3000
Manage and query configuration values:
expose config list
expose config get port
π Architecture & Design Principles
expose/
βββ cmd/expose/ # CLI entry point (Cobra)
βββ internal/
β βββ cli/ # Cobra subcommands & flag parsing
β βββ config/ # YAML config management
β βββ provider/ # Tunnel providers (LocalTunnel, Cloudflare, Self-Hosted)
β βββ server/ # Self-hosted relay control & public server
β βββ tunnel/ # Core service orchestration & lifecycle
β βββ version/ # Build metadata & version injection
βββ .expose.yml # Local project configuration
- Interface-Driven: Extensible
Provider interface makes adding new tunnel backends straightforward.
- Clean Separation: CLI β Service β Provider (strict unidirectionality, zero circular dependencies).
- Fully Testable: High test coverage (>80%) across packages with race detector validation (
go test -race).
π§ͺ Development & Testing
# Run all unit tests with Go race detector
go test ./... -v -race -cover
# Package-specific coverage
go test ./internal/tunnel -cover
go test ./internal/server -cover
go test ./internal/provider -cover
π€ Contributing
Contributions are welcome! Please check out CONTRIBUTING.md for branch strategy, test requirements, and code style guidelines.
π License
Distributed under the Apache License 2.0. See LICENSE for details.
Made with β€οΈ by @kernelshard