Slice and share your machine on a public URL: a port, a shell, a coding agent, a container.
1.6K
Put what's running on your machine on a public URL. A port, a shell, a coding agent, a container: one command, no account.
npx tunneld
All you need is Node 20 or later. npx fetches tunneld and runs it:
npx tunneld :3000
tunneld v0.0.68 (libtunnel v0.1.11, built go1.26.5, cache 99053a798931fe97)
https://0t8qsb6pq3.tunneld.domain/
-> http://localhost:3000
Press Ctrl+C to stop the tunnel...
That address works for anyone, anywhere, over HTTPS. On a desktop a browser tab opens on it too. Press Ctrl+C and it's gone. Run the same command from the same directory later and you usually get the same address back.
Start your app before or after: your address comes up either way. Until
something answers on your port, a visitor gets a page asking for it, which loads
your app on its own once it's up. Nothing to share at all? Run npx tunneld on
its own to share your shell.
npm install -g tunneld, then drop the npx from
everything below.go install github.com/tunnel-pizza/tunneld@latestghcr.io/tunnel-pizza/tunneld, or tunneld/tunneld on
Docker Hub (tunneld/debug for kubectl debug). See
Running in a containerā .| Command | What you get | |
|---|---|---|
| š | npx tunneld :3000 | Whatever is listening on port 3000, on a public URL. |
| š | npx tunneld | Your $SHELL, as a terminal in a browser tab. |
| š¤ | npx tunneld claude --resume | A coding agent you can keep working with from your phone or another computer. |
| āļø | npx tunneld nvim | Your editor, config and plugins included, in a browser tab. |
| šŖ | npx tunneld :3000 :4000 | Two apps on one hostname, side by side in one window. |
| š | npx tunneld :3000 'npm run dev' claude | npm run dev, your app, and a Claude session beside it. |
| š³ | npx tunneld attach://dockerd/web | A running container's terminal. Compose service names work too. |
Anything that runs in a terminal works: htop, a REPL, Codex, Gemini CLI,
OpenCode. The words after a program are the program's own, up to the next
:port or URL.
kubectl debug starts tunneld beside whatever you are debugging, with nothing
installed in the cluster beforehand. In a pod, a bare run finds the pod's own
web apps and puts them on the address beside a shell. Press Enter to stop the
tunnel; Ctrl+C only detaches you and leaves it running.
| Command | What you get | |
|---|---|---|
| š¦ | kubectl debug <pod> --image tunneld/debug -i | The pod's web apps and a shell on one URL, without touching the pod's own containers. |
| āøļø | kubectl debug node/<node> --image tunneld/debug --profile=sysadmin -i | A root shell on the node. chroot /host is the node's own filesystem. |
That address is root on the node: send it only to someone you would hand the node to.
The tunneld plugin puts your tunnels in front of Claude as MCP tools: it lists them, runs commands in a tunnel's terminal, and reads its screen. Add tunnel.pizza's marketplace once, install it, then sign in to tunnel.pizza:
claude plugin marketplace add https://tunnel.pizza
claude plugin install tunneld@tunnel-pizza
claude mcp login plugin:tunneld:tunnel-pizza
To get new releases automatically, open /plugin, choose the tunnel-pizza
marketplace, and turn on auto-update.
One program (npx tunneld claude): the address is printed, then the
terminal takes over your console inside a frame. The same terminal is on the
web, so you, your phone, and anyone you send the address to type into one
session.
āā exec:///usr/local/bin/claude āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā https://0t8qsb6pq3.tunneld.domain/ ā®
ā > ā
ā ā
ā°ā ^K commands ā tunneld v0.0.68 (libtunnel v0.1.11, built go1.26.5) āā my-laptop (2 viewers) [102Ć2] āÆ
Several origins (npx tunneld :3000 bash): the address opens a panel with
one tile per origin, the app in one and the shell's terminal in the other. Each
tile also has an address of its own, ?0, ?1 and so on, for sending just
that one.
A phone: press Ctrl+K then r in any frame and the address shows up as a QR code. Point the camera at it.
Inside a frame, on your console or in a tab, every key goes to the program except one.
| Key | |
|---|---|
| Ctrl+K | Opens the frame's commands. The program never sees it. |
| then q | The ways out, on a row of their own: |
| then q q | Quit. Ends the run: the tunnel, every origin, and every program it started. |
| then q r | Restart the program for everyone watching. The address stays. |
| then q d | Detach. This screen lets go and the session carries on. |
| then r | Shows the address as a QR code, for a phone. |
| then l | Shows tunneld's own recent log lines. |
| then c | Copies everything: the history and the screen. |
| then f | Follow on or off. Off, the view stays put while output arrives below it; on is the live screen again. |
| then esc | Never mind. |
| wheel | Scrolls back through what went past. Any key returns to the live screen. |
| drag | Selects, and copies to your clipboard. Double-click a word, triple-click a row, Shift-click to extend. |
| double-click the address | Copies the tunnel's address. Cmd/Ctrl+click opens it. |
On a container, Ctrl+C and Ctrl+D ask to be pressed twice, because once its main process exits there's nothing to come back to. On a program they go straight through, since a program can always be started again.
Every argument is an origin, and they all share one hostname. The first is the
default. Each one after it answers on a bare ?n, where n is its position:
npx tunneld :3000 :4000
A browser sticks with the origin it landed on, so a frontend on :3000 and
its API on :4000 both work behind a single address. To get back to the first
one, use ?0 rather than a bare /.
A dev server that opens its own WebSocket (live reload, say) needs one more
character. Mark its origin +ws and every unrouted handshake goes there:
npx tunneld :4000 http+ws://localhost:5173
The details, and why they are the way they are, are in the referenceā .
A bare word that names a program on your $PATH runs that program on a real
pseudo-terminal, and the page shows its screen. The program starts when
someone first looks at it, on your console or in a tab. When it ends, the page
offers to start it again.
npx tunneld htop
npx tunneld :3000 htop -d 5 # a service, then htop with its own arguments
npx tunneld 'next dev' bash # quote a program together with its arguments
attach://dockerd/<name> attaches to a running container the way
docker attach does. <name> can be a container name, an id, or a Compose
service name:
npx tunneld attach://dockerd/web
More in the reference: programsā , containersā , the frameā .
The address is the only key. Anyone who has it reaches what's behind it, and for a terminal that means a shell on your machine. Hostnames are random and hard to guess, but send one the way you'd send a password, and press Ctrl+C when you're done.
If you're signed in to the GitHub CLI, the mint says who's asking, and you can
see your tunnels at https://tunnel.pizza/<your-login> once you sign in there.
In a Claude Code workspace, it sends the workspace's own credential instead
when there's no GitHub one. --identity-providers= sends nothing. See
Identityā .
Flags go before the origins, the way docker run's go before the image. Every
flag has a TUNNELD_* environment variable, and the flag wins.
| Flag | Variable | |
|---|---|---|
--no-cache | TUNNELD_NO_CACHE | A fresh address every run. |
--multiview=false | TUNNELD_MULTIVIEW | No panel; each origin keeps only its own ?n address. |
--identity-providers= | TUNNELD_IDENTITY_PROVIDERS | Mint anonymously. |
--qr | TUNNELD_QR | Print your address as a QR code too, and open no tab. Scan it with your phone. |
--log-level debug | TUNNELD_LOG | Log to stderr. Silent by default. |
--provider <host> | TUNNELD_PROVIDER | Mint against another provider. Default tunnel.pizza. |
| (the arguments) | TUNNELD_ORIGINS | Origins, comma-separated. |
| ā | TUNNELD_WWW_AUTHENTICATE | Password-protect the tunnel, or let in only the people it lists (Single Sign-On). Environment only: a command line is readable by every user. See Protectionā . |
stdout carries the public addresses and nothing else, one per line, so they're
easy to pipe. Everything meant for a person goes to stderr. tunneld version
prints the build.
Every flag and variable in full: docs/reference.mdā .
Under npx, -d as the first word detaches: the addresses print, the prompt
comes back, and the tunnel stays up. npx tunneld -k ends every tunneld run
on the machine, detached or not, the way Ctrl+C would. -kd does both, a
restart. All three belong to the npm launcher, not to tunneld, and aren't
available on Windows yet.
npx tunneld -d :3000 claude
npx tunneld -kd :3000 claude # end every run, start this one detached
npx tunneld -k
The launcher also refuses a line that could mean two things:
npx tunneld claude "next dev" is, as written, one program, claude with
next dev as its argument, since a bare program takes the words after it. It
stops and shows the quoting for each reading: 'next dev' claude for two
origins, 'claude "next dev"' for one. See The npm launcherā .
tunneld is the command-line client for tunnel.pizzaā .
It asks tunnel.pizza for a Cloudflare Tunnel and a hostname, then runs the
tunnel in-process through libtunnelā , so
there is no cloudflared to install. Your traffic goes between Cloudflare's
edge and your machine, not through tunnel.pizza, which is only the control
plane. The one exception is a network that blocks the edge's port 7844: there
the connection goes through tunnel.pizza's relay, which forwards the encrypted
bytes without reading them.
The command is a builder, so another Go program can mount it under a verb of its own, with the same flags, help, and behaviour:
package main
import (
"context"
"os"
"github.com/tunnel-pizza/tunneld/v1alpha1"
)
func main() {
cmd := v1alpha1.New(
v1alpha1.WithName("expose"), // mount under your own verb
v1alpha1.WithOrigin("http://localhost:3000"), // a default the user can override
).Command()
if err := cmd.ExecuteContext(context.Background()); err != nil {
os.Exit(1)
}
}
The options, Run for a tunnel without a CLI, and runnable examples are in
docs/embedding.mdā .
tunneld is a thin layer over other people's work. Thank you to:
github.com/cnuss/libtunnel):
the tunnel engine, behind every mint and every connection to the edge.github.com/cloudflare/cloudflared): Cloudflare's tunnel protocol and edge
client, linked in through libtunnel.k8s.io/cri-streaming): Kubernetes' streaming library, which every terminal
tunneld serves, a container's or a program's, is attached through.nhooyr.io/websocket, its former name) and
gorilla/websocketā
(github.com/gorilla/websocket): the WebSockets. coder's arrives through
cloudflared. gorilla's is tunneld's own direct dependency, which its tests
dial a terminal with, and cloudflared links it too.charm.land/bubbletea/v2), Ultravioletā
(github.com/charmbracelet/ultraviolet), x/ansiā
(github.com/charmbracelet/x/ansi), x/vtā
(github.com/charmbracelet/x/vt) and colorprofileā
(github.com/charmbracelet/colorprofile). The whole frame is built on them,
on your console and in a tab.src.elv.sh): the shell built into tunneld,
the one a run with no origin serves when this machine has no $SHELL,
zsh, fish, bash, ksh, mksh, dash, ash or sh of its own.github.com/u-root/u-root):
the commands that shell brings for a machine that has none of its own ā
every one of u-root's that builds there, from ls, cat and grep to
wget, tar and sshd ā rewritten into tunneld by
gobusyboxā , u-root's busybox builder.github.com/modelcontextprotocol/go-sdk): the Model Context Protocol
server tunneld serves to agents on its control path.@xterm/[email protected], with
@xterm/[email protected], @xterm/[email protected] and
@xterm/[email protected]. It is the terminal in a browser tab, loaded
from jsDelivr when the page opens.xterm-256color terminfo entry the Linux binaries
carry, for programs served through the terminal shim on machines without one.And the rest of what tunneld requires directly:
And Go itself. The license notices for the Go standard library and for every
module a release binary links are in THIRD_PARTY_LICENSES, attached to each
releaseā and shipped in the
npm package.
CONTRIBUTING.mdā has the layout, the dev loop, the conventions, and how a change gets from an issue to a release. Security reports go through SECURITY.mdā .
FSL-1.1-MITā , the Functional Source License. Use, copy, modify, and redistribute it for any purpose except a product that competes with tunneld. Each version becomes plain MIT two years after its release.
Content type
Image
Digest
sha256:1434ede99ā¦
Size
21.4 MB
Last updated
about 6 hours ago
docker pull tunneld/debug