Sign inSign up

tunneld/debug

By tunneld

•Updated about 6 hours ago

Slice and share your machine on a public URL: a port, a shell, a coding agent, a container.

Image
0

1.6K

tunneld/debug repository overview

⁠tunneld

Put what's running on your machine on a public URL. A port, a shell, a coding agent, a container: one command, no account.

npm CI Go Reference OpenSSF Scorecard License: FSL-1.1-MIT

npx tunneld

⁠Try it

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.

Other ways to install
  • npm, for good: npm install -g tunneld, then drop the npx from everything below.
  • Go: go install github.com/tunnel-pizza/tunneld@latest
  • A binary: every release⁠ carries a signed one per platform, and SECURITY.md⁠ has the recipe to verify it.
  • A container: ghcr.io/tunnel-pizza/tunneld, or tunneld/tunneld on Docker Hub (tunneld/debug for kubectl debug). See Running in a container⁠.

⁠What you can share

CommandWhat you get
šŸ”Œnpx tunneld :3000Whatever is listening on port 3000, on a public URL.
🐚npx tunneldYour $SHELL, as a terminal in a browser tab.
šŸ¤–npx tunneld claude --resumeA coding agent you can keep working with from your phone or another computer.
āœļønpx tunneld nvimYour editor, config and plugins included, in a browser tab.
🪟npx tunneld :3000 :4000Two apps on one hostname, side by side in one window.
šŸ•npx tunneld :3000 'npm run dev' claudenpm run dev, your app, and a Claude session beside it.
🐳npx tunneld attach://dockerd/webA 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.

⁠Kubernetes

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.

CommandWhat you get
šŸ“¦kubectl debug <pod> --image tunneld/debug -iThe 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 -iA 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.

⁠Claude Code

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.

⁠What you see

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.

⁠Keys

Inside a frame, on your console or in a tab, every key goes to the program except one.

Key
Ctrl+KOpens the frame's commands. The program never sees it.
then qThe ways out, on a row of their own:
then q qQuit. Ends the run: the tunnel, every origin, and every program it started.
then q rRestart the program for everyone watching. The address stays.
then q dDetach. This screen lets go and the session carries on.
then rShows the address as a QR code, for a phone.
then lShows tunneld's own recent log lines.
then cCopies everything: the history and the screen.
then fFollow on or off. Off, the view stays put while output arrives below it; on is the live screen again.
then escNever mind.
wheelScrolls back through what went past. Any key returns to the live screen.
dragSelects, and copies to your clipboard. Double-click a word, triple-click a row, Shift-click to extend.
double-click the addressCopies 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.

⁠Several things on one address

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⁠.

⁠Programs and containers

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⁠.

⁠Before you share

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⁠.

⁠Configuration

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.

FlagVariable
--no-cacheTUNNELD_NO_CACHEA fresh address every run.
--multiview=falseTUNNELD_MULTIVIEWNo panel; each origin keeps only its own ?n address.
--identity-providers=TUNNELD_IDENTITY_PROVIDERSMint anonymously.
--qrTUNNELD_QRPrint your address as a QR code too, and open no tab. Scan it with your phone.
--log-level debugTUNNELD_LOGLog to stderr. Silent by default.
--provider <host>TUNNELD_PROVIDERMint against another provider. Default tunnel.pizza.
(the arguments)TUNNELD_ORIGINSOrigins, comma-separated.
—TUNNELD_WWW_AUTHENTICATEPassword-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⁠.

⁠In the background

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⁠.

⁠How it works

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.

⁠Use it from Go

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⁠.

⁠Acknowledgements

tunneld is a thin layer over other people's work. Thank you to:

  • libtunnel⁠ (github.com/cnuss/libtunnel): the tunnel engine, behind every mint and every connection to the edge.
  • cloudflared⁠ (github.com/cloudflare/cloudflared): Cloudflare's tunnel protocol and edge client, linked in through libtunnel.
  • cri-streaming⁠ (k8s.io/cri-streaming): Kubernetes' streaming library, which every terminal tunneld serves, a container's or a program's, is attached through.
  • coder/websocket⁠ (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⁠: Bubble Tea⁠ (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.
  • Elvish⁠ (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.
  • u-root⁠ (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.
  • MCP Go SDK⁠ (github.com/modelcontextprotocol/go-sdk): the Model Context Protocol server tunneld serves to agents on its control path.
  • xterm.js⁠: @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.
  • ncurses: the compiled 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:

ModuleFor
github.com/creack/pty⁠The pseudo-terminal behind every program and the shell fallback.
github.com/moby/moby/client⁠, github.com/moby/moby/api⁠, github.com/containerd/errdefs⁠attach://dockerd.
github.com/spf13/cobra⁠, github.com/spf13/pflag⁠, github.com/spf13/viper⁠The command line and its environment.
github.com/yuin/goldmark⁠Messages of the day.
rsc.io/qr⁠The QR code a phone reads.
github.com/shirou/gopsutil/v4⁠How much memory is free, which sizes the terminal's history.
github.com/go-jose/go-jose/v4⁠Checking the tokens the provider signs, for Single Sign-On.
github.com/pkg/browser⁠Opening the tab.
github.com/go-logr/logr⁠, k8s.io/klog/v2⁠cri-streaming's logs, routed into tunneld's own.
golang.org/x/sys⁠, golang.org/x/term⁠The system calls under a program's terminal, and raw mode on your console.
github.com/cenkalti/backoff/v4⁠, github.com/dustin/go-humanize⁠, github.com/florianl/go-tc⁠, github.com/gliderlabs/ssh⁠, github.com/google/go-tpm⁠, github.com/google/uuid⁠, github.com/gopacket/gopacket⁠, github.com/insomniacslk/dhcp⁠, github.com/ishidawataru/sctp⁠, github.com/jaypipes/ghw⁠, github.com/kevinburke/ssh_config⁠, github.com/mdlayher/vsock⁠, github.com/packetcap/go-pcap⁠, github.com/pkg/sftp⁠, github.com/ProtonMail/go-crypto⁠, github.com/rck/unit⁠, github.com/sirupsen/logrus⁠, github.com/u-root/cpuid⁠, github.com/u-root/gobusybox/src⁠, github.com/u-root/iscsinl⁠, github.com/u-root/uio⁠, github.com/vishvananda/netlink⁠, github.com/vishvananda/netns⁠, github.com/vtolstov/go-ioctl⁠, go.bug.st/serial⁠, golang.org/x/crypto⁠, golang.org/x/exp⁠, golang.org/x/net⁠, golang.org/x/text⁠, golang.org/x/tools⁠, gopkg.in/yaml.v2⁠, pack.ag/tftp⁠What u-root's commands in the built-in shell import, carried in with them.

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

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⁠.

⁠License

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.

Tag summary

Content type

Image

Digest

sha256:1434ede99…

Size

21.4 MB

Last updated

about 6 hours ago

docker pull tunneld/debug