Moving away from Tailscale to Iroh
A clear move to make networking disappear and improve UX dramatically.
Self-hosted music has a familiar shape. A serious library lives on a desktop machine or a NAS: terabytes of data organised across endless folder structures, years of rips, purchases, and carefully kept files. At home, that archive feels close. Since day one, the point of Tape Music Suite has been to ease the complexity of the scattered world of self-hosting your own archive, making it possible to remotely control and work with it as easily as you use Google Drive, but built around the specific needs of music — which are not a short list.
Leave the house with a phone and you want the same catalog: browse, search, play, offline where it matters. Not a second library rebuilt in a streaming app. Not another endless setup on each device. One cohesive language for working with it.
That requirement is where a lot of stacks quietly turn into networking projects. Port forwards, dynamic DNS, reverse proxies, "just put it behind a tunnel," a second copy of the collection "for mobile," or a mesh VPN that makes the remote phone look local. Each path can be made to work. Each path also asks the listener to become a part-time network operator before they get to hear their own music. For Tape Music Suite that was the wrong tax on the critical path.

One catalog. Desk hub and phone on the same archive.
I needed desk→phone to feel like an extension of one hub, not like joining another product's network. Tailscale was the honest first path. WireGuard underneath is solid. On the wire it did the job.
It failed install Tape and use it. Tailscale had to live on the phone and the desktop. Someone had to create an account in someone else's identity system. That client had to stay installed and healthy. Tailscale remains a strong tool for a LAN. It is a machine-level mesh: the device joins an overlay. Tape needed Desktop, Mobile, and Docker to talk as Tape. Selective. Multi-device. Inside the suite.
Why Iroh
That search led me to Iroh. Iroh is the most direct way I found to do selective, multi-device, application-level networking: each Tape app is an endpoint, you dial who you paired, peer-to-peer when there is a direct path and relay when there is not.
In the product that path is Tape Private Network. No Tailscale install for the core desk→phone path. Pairing aimed at your own devices rather than a public media server. The hub stays the source of truth; Iroh carries API, artwork, playback. Each Tape app owns its endpoint. Future clients join that graph, not a device-wide VPN. That is why I do not call it a personal VPN.
WireGuard is the crypto block. Tailscale is a mesh of the machine. Iroh is an embedded peer-to-peer transport inside Tape: dial by cryptographic identity over authenticated QUIC, punch NATs, fall back through encrypted relays. The music apps already know how to find each other.
Iroh does not decide who may browse. Pairing binds a hub to its endpoint. Presence helpers are not trusted to grant access. Once the transport is open, Tape still verifies the application session. The existing library API — artwork, HLS, playback — rides a virtual HTTP tunnel. Iroh is not a second backend.
That is the UX change. User ease first: install Tape, pair, listen. Expandability second: another Tape computer as a player, a NAS hub without a VPN sidecar in the music path, clients we have not shipped yet on the same application network.
Why not WebRTC or libp2p
App-level networking is not unique to Iroh. WebRTC and libp2p also live in the process. The difference is how you dial.
WebRTC is a call: SDP, ICE, a signaling server. Identity is this session, not this hub. Tape's API on a DataChannel means inventing the tunnel and the signaling.
libp2p has peer IDs, hole punch, and relay. It is a kit. You can assemble the same shape. It does not give you connect(id) as the product.
Iroh is an endpoint, a stable key, a ticket, QUIC. Same API for direct and relay. No signaling product. No OS overlay. Fewest moving parts between pair and session, for this kind of app.
React Native had no official path
Tape Mobile is one React Native codebase for iOS and Android. n0 had nothing published for that runtime. iroh-ffi is Python, Swift, Kotlin, and Node. Official mobile is two native apps. Node JavaScript is N-API — it does not run in Hermes.
I built a custom native module: Iroh's Rust crates compiled into both phone platforms, bound with uniffi-bindgen-react-native. Not a reimplementation of the protocol. Small surface: endpoint, ticket, QUIC, framed bytes (length-prefixed, capped at 2 MiB, bounded queues). Tape owns pairing and failure UX. That module is what made Tape Private Network ship on the phone.
Open: gordo-labs/iroh-react-native-bridge, @gordo-labs/react-native-iroh 0.2.0 (beta). n0 still has no React Native package. If they ship one, I would rather converge than keep a parallel universe.
The QUIC connection was not the product
A phone played through Iroh from another network for most of a day. A MacBook joined the same hub and would not stay up. Restarting the hub brought both back. The Mac got HTTP 304 and reconnect-looped. It looked like Iroh could not handle a second peer. Iroh was not the problem.
The desktop client minted a fresh Ed25519 identity on every reconnect. The HTTP layer built a body for 304, which Fetch rejects — that could kill the accept loop. Dialing sometimes started before the client knew its home relay. Fixes: one persisted identity per install, bodyless statuses, an accept loop that survives a failed peer, wait for relay info, ignore late reconnects.
"The QUIC connection opened" is not "the product connection works." Identity, authentication, framing, and recovery stay with Tape. Iroh is a very good primitive. It does not make the rest of the system disappear.
What Iroh bought us — and what is next
Iroh is simpler sovereign app networking. For an app like Tape — local hub, phone companion, one catalog — that jump is what made the experience dramatically easier, and the structure that can grow without shoving every new device onto someone else's machine mesh.
Traffic is end-to-end encrypted. Public relays work today. We still have to choose how we run capacity at scale: managed, dedicated, or self-hosted.
On the same connection model, an experimental Tape Music Hub Network: metadata search across trusted hubs. Not a shipping claim. No audio, paths, or inventory. Playback stays on the personal hub.
I'm exploring relay infrastructure that is not a vendor default: a decentralised model via Nostr. That appeals to me a lot. Until then, relays remain managed, dedicated, or self-hosted — on top of the application network Iroh already gives Tape.
Putting the connection inside Tape — Iroh as Tape Private Network, including the React Native module I had to build myself — is how I got the UX I wanted to ship, and made the remaining failures mine to fix.
Product: tapemusicsuite.com · Bridge: gordo-labs/iroh-react-native-bridge · npm: [@gordo-labs/react-native-iroh](https://www.npmjs.com/package/@gordo-labs/react-native-iroh)