# How to Use Iroh as a Decentralized Storage Solution

> Learn how to use Iroh, a decentralized storage solution, to publish and retrieve blobs over a P2P QUIC transport without centralized servers. Explore this Rust framework today.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-04

---

**Iroh is a Rust-first framework that implements fully decentralized, content-addressed storage over a peer-to-peer QUIC transport, allowing you to publish and retrieve BLAKE3-hashed blobs without centralized servers.**

Iroh provides a complete peer-to-peer storage layer that eliminates the need for centralized cloud providers by combining content-addressed storage with public-key routing. According to the n0-computer/iroh source code, the framework exposes a high-level API that handles NAT traversal, encrypted transport, and data integrity automatically. This guide explains how to use iroh as a decentralized storage solution in your Rust applications, referencing the actual implementation files and methods.

## Core Architecture Components

Three main building blocks work together to provide decentralized storage in iroh. Each component handles a specific layer of the networking and storage stack.

### The Endpoint (Transport Layer)

The **Endpoint** type defined in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) serves as the public-key-identified node that can dial and be dialed. When you call `Endpoint::bind()`, the framework generates a long-term Ed25519 key pair and creates a QUIC endpoint that hides complexity such as hole-punching, relay fallback, and DNS resolution.

The endpoint owns a QUIC socket and a router, managing connection lifecycles while authenticating all traffic via TLS-like encryption using the node's public key.

### Content-Addressed Blob Storage

The **iroh-blobs** protocol implements BLAKE3-hash-addressed blob transfer. When you store data using `BlobClient::put()`, iroh computes the BLAKE3 hash and returns a **Tag** that uniquely identifies the blob. This content-addressed approach means the hash itself is the immutable identifier—no additional metadata is required to locate the data.

The blob client communicates over QUIC streams that are automatically multiplexed, prioritized, and encrypted. The type lives in the external `iroh-blobs` crate, which is pulled in automatically as a workspace dependency.

### Relay and Discovery Services

Iroh includes infrastructure for peer discovery and connectivity:

- **Relay servers** ([`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)) provide stateless forwarding of encrypted QUIC packets when direct hole-punching fails
- **DNS/Pkarr resolution** ([`iroh-dns-server/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns-server/src/dns.rs)) maps human-readable names like `mybox.pkarr.net` to endpoint IDs defined in [`iroh-base/src/endpoint_addr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/endpoint_addr.rs)

These services ensure storage remains accessible even when peers are behind restrictive NATs.

## Publishing and Retrieving Blobs

The following examples demonstrate the complete workflow for decentralized storage operations. These patterns are adapted from [`iroh/examples/transfer.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/transfer.rs).

### Creating an Endpoint and Storing Data

To publish data, bind a local endpoint and use the blob client to compute the content hash:

```rust
use iroh::endpoint::Endpoint;
use iroh::blob::Client as BlobClient;
use tokio::fs;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Bind a local endpoint (generates a new key pair on first run)
    let endpoint = Endpoint::bind().await?;
    
    // Create a blob client that talks over this endpoint
    let blobs = BlobClient::new(endpoint.clone());
    
    // Read data you want to store
    let data = fs::read("my_file.bin").await?;
    
    // Put the data – iroh computes the BLAKE3 hash for you
    let tag = blobs.put(data.into()).await?;
    println!("Stored blob with tag {}", tag);
    
    Ok(())
}

```

The `BlobClient::put()` method streams the data over a QUIC connection and returns a `Tag` containing the BLAKE3 hash. This tag serves as the permanent address for retrieving the blob from any peer in the network.

### Retrieving Blobs by Hash

To download data, create an endpoint on the receiving side and request the blob by its tag:

```rust
use iroh::endpoint::Endpoint;
use iroh::blob::Client as BlobClient;
use tokio::fs;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let endpoint = Endpoint::bind().await?;
    let blobs = BlobClient::new(endpoint.clone());
    
    // The tag obtained from the publishing side
    let tag = "b7c9f6a5…".parse()?; // Tag implements FromStr
    
    // Request the blob – iroh finds the owning peer automatically
    let data = blobs.get(tag).await?.into_bytes();
    fs::write("downloaded.bin", data).await?;
    println!("Blob downloaded");
    
    Ok(())
}

```

When `BlobClient::get()` is called, iroh locates the owning peer via the public-key network, attempts a direct connection (hole-punch), and falls back to a public relay if needed. The blob streams back to the caller without requiring a central server.

### Using Human-Readable Names with PKARR

Instead of sharing raw public keys or tags, you can use DNS/Pkarr resolution for friendly naming:

```rust
use iroh::endpoint::Endpoint;
use iroh::blob::Client as BlobClient;
use iroh::dns::Resolver;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let endpoint = Endpoint::bind().await?;
    let blobs = BlobClient::new(endpoint.clone());
    
    // Resolve a PKARR name to an endpoint address
    let resolver = Resolver::default();
    let remote = resolver.resolve("mybox.pkarr.net").await?;
    endpoint.add_known_peer(remote).await?;
    
    // Now request a blob by its tag as before
    // ...
    Ok(())
}

```

The `Resolver` type handles the PKARR DNS resolution logic, allowing peers to discover each other via memorable domain names rather than cryptographic identifiers.

## Key Implementation Details

Understanding these specific implementation files helps when debugging or extending iroh's storage capabilities:

- **[`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)** – Core `Endpoint` type that manages QUIC sockets, routing, and connection lifecycles
- **[`iroh-base/src/endpoint_addr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/endpoint_addr.rs)** – Definition of `EndpointAddr` and `EndpointId`, the public-key-based identifiers used throughout the system
- **[`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)** – Minimal relay implementation used when direct hole-punching fails
- **[`iroh-dns/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/lib.rs)** and **[`iroh-dns-server/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns-server/src/dns.rs)** – DNS/Pkarr resolution logic for peer discovery

All traffic is authenticated by the endpoint's public key, enabling peers to verify that a blob originates from the claimed source. The protocol uses **noq** (a QUIC implementation) to provide low latency, stream-level flow control, and datagram support while avoiding head-of-line blocking.

## Summary

- **Iroh implements decentralized storage** through a Rust-first framework combining QUIC transport with content-addressed blobs
- **Content-addressing via BLAKE3** ensures data integrity—hashes serve as permanent tags that identify blobs without central indexing
- **Automatic NAT traversal** attempts direct hole-punching before falling back to stateless relay servers
- **Public-key routing** identifies endpoints via Ed25519 keys, with optional PKARR DNS resolution for human-readable names
- **Simple API surface** exposes `Endpoint::bind()` and `BlobClient` methods for publishing and retrieving data across peer-to-peer networks

## Frequently Asked Questions

### What makes iroh different from IPFS?

While both systems use content-addressing, iroh focuses specifically on efficient blob transfer over QUIC with built-in hole-punching and relay fallback. Unlike IPFS, iroh does not require a distributed hash table (DHT) for content routing—instead, it uses public-key-based endpoint identification and direct peer connections, resulting in lower latency and simpler deployment.

### How does iroh handle NAT traversal?

Iroh first attempts to open a direct UDP path through hole-punching between endpoints. If NAT traversal fails, the connection automatically falls back to a public relay server ([`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)). The relay is stateless and merely forwards encrypted QUIC packets, meaning it cannot inspect traffic content while enabling connectivity between restricted networks.

### What is the relationship between Tags and BLAKE3 hashes?

A **Tag** is the high-level identifier returned by `BlobClient::put()` that wraps the BLAKE3 hash of the blob content. Because the hash is cryptographically unique to the data, the tag serves as an immutable, verifiable pointer to the blob. When you request data via `BlobClient::get()`, iroh verifies the received content against the hash embedded in the tag to ensure integrity.

### Can I use iroh from languages other than Rust?

Yes, while iroh is Rust-first, the framework provides `iroh-ffi` bindings that expose the storage API to other languages. The core concepts—creating an endpoint, obtaining tags, and retrieving blobs—remain identical across language bindings, though the Rust implementation in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) represents the reference implementation for the decentralized storage protocol.