← All updates

Private collaboration over Radicle Garden

A step-by-step guide for two collaborators behind NAT working together on a private Radicle repository with Garden as the always-on relay between them.

Published by The Radicle Garden Team

The scenario

Alice and Bob want to work on a private repository together. The setup is the one most of us live with:

  • Alice’s laptop is behind a residential NAT at home.

  • Bob’s laptop is behind a corporate NAT at work.

  • Neither machine is reachable from the open internet; when one of them is offline, the other has no way to fetch.

  • The code is private. They don’t want it on a public seed.

This post walks through wiring up a Radicle Garden seed as the always-on relay between them. Garden picks up new refs whenever either of them is online, and the other catches up next time they connect.

How it works at a glance

Three nodes are involved, but only one is on a public address:

   Alice's laptop                 Bob's laptop
   (NAT, sometimes online)        (NAT, sometimes online)
                  \              /
                   \            /
                    v          v
              ┌──────────────────────┐
              │  Garden seed node    │
              │  (always-on, public) │
              └──────────────────────┘

When Alice pushes a commit, her node announces the new git refs. Garden, which is always reachable, fetches them. The next time Bob is online, his node sees the announcement on Garden and pulls them down. The two laptops never need to be online simultaneously, and they never need to reach each other directly.

For this to work safely with a private repo, three things have to be true:

  1. Garden’s seed Decentralised IDentifier (DID) is in the repository’s allow list, so Garden is permitted to fetch.

  2. Both Alice and Bob are either delegates or in the allow list.

  3. Both their local nodes are (see configured to talk to Garden).

Visibility is enforced at the protocol level; only delegates and DIDs in allow can know about a repository. Any other peer that happens to gossip with Garden will never hear about the private repo.

Prerequisites

  • Both Alice and Bob have a working Radicle setup (see the prerequisites in the always-on-node guide).

  • One of them, let’s say Alice, has an active Radicle Garden subscription. The Garden dashboard shows the seed’s connect address (<garden-did>@<host>:<port>) and its bare DID.

  • Both local nodes are running.

You only need one Garden subscription between the two of them. Bob doesn’t need his own, he just talks to Alice’s Garden as a seed. In this scenario, the Garden seed operates as a team seed.

Before you start, each person should grab their own DID:

rad self --did

…and share it with the other. We’ll refer to them as <alice-did>, <bob-did>, and the Garden’s seed DID as <garden-did> throughout.

Step 1 — Alice creates the repo

Inside the working copy:

rad init --name our-project --description "Shared private work" --private --no-confirm

The --private flag sets the visibility to private on the identity document. Copy the rad:z… RID that’s printed; Bob will need it.

Step 2 — Alice allow-lists Garden, then tells Garden to seed

Garden is not a delegate, and won’t ever be, but it needs to be in allow so it can fetch and seed the repo:

rad id update --title "Allow Garden to seed" --allow did:key:<garden-did>
rad sync

Then, we need to make sure Alice’s node is directly connected to her Garden node, because other nodes won’t gossip messages about a repo they don’t know about. To ensure Alice’s node will always connect to her Garden node, she runs:

rad config push preferredSeeds <garden-connect-address>
rad config push node.connect <garden-connect-address>

She can verify her node is connected with rad node status.

Finally, on the Garden dashboard, click Add a repo and paste the rad:z… RID from Step 1. Garden will start fetching from Alice in the background. The dashboard will flip to "synced" once it has Alice’s refs.

Step 3 — Alice adds Bob as a delegate

Bob doesn’t need to be allow-listed first - as we did with the Garden node - Alice can instead add him as a delegate straight away.

rad id update --title "Add Bob as delegate" --delegate did:key:<bob-did>
rad sync

Delegates can push to the canonical branches and co-sign future identity updates. They are also implicitly allowed to view the repo (no separate allow entry needed).

Step 4 — Bob configures Garden and clones

On Bob’s machine, point his node at Garden:

rad config push preferredSeeds <garden-connect-address>
rad config push node.connect <garden-connect-address>

Then clone the repo by RID, telling rad to fetch from the Garden seed:

rad clone <rad:z...> --seed <garden-nid>

Bob’s node fetches the identity doc and Alice’s working refs directly from Garden. Once it’s done, Bob has a checkout he can work in:

cd ./our-project

Step 5 — Bob makes his first commit

The setup is done. To confirm everything works end-to-end, Bob commits and pushes (e.g. adding a README to the project):

echo "# our-project" > README.md
git add README.md
git commit -m "Initial README"
git push rad

git push rad writes Bob’s refs into his local Radicle storage and announces them. Garden, being a preferred seed and an allow-listed peer, fetches them. The next time Alice runs rad sync --fetch, she’ll pull Bob’s refs from Garden.

Daily flow

Once both setups are done, the rhythm is just:

  • Hack locally.

  • git commit -m '…​' && git push rad to publish your refs through Garden.

  • rad sync --fetch to pull whatever the other person has done.

Neither side cares whether the other is currently online. Garden is always online, so the announcements and refs are always somewhere reachable!

Optional: require both delegates to sign off

If you want stronger sign-off, two-of-two approval for changes to the canonical default branch, Alice can raise the identity threshold to 2. Raising the threshold does require Alice to have already fetched Bob’s refs, because Radicle now needs to verify that every delegate has signed refs reachable locally. Concretely, Alice should run this only after Bob has pushed at least once:

rad follow did:key:<bob-did> --alias bob
rad sync --fetch
rad id update --title "Require both delegates" --threshold 2
rad sync

After this, every change to the canonical branch requires both Alice’s and Bob’s signed refs to converge.

Optional: Adding more collaborators

The pattern scales by repeating Step 3 and Step 4 for each new person: an existing delegate adds the newcomer with rad id update --delegate did:key:<new-did>, and the newcomer clones through Garden.

With more than two delegates, identity updates need a quorum (votes >= delegates / 2 + 1), so any new delegate addition has to be accepted by enough existing delegates to clear that bar.

Warning
While a repository with 2 delegates sounds like better practice than having a single delegate, because of the above quorum requirement, it is actually no better (!). Losing access to either of the delegates' keys can mean no further updates to the identity document would be possible: a new repository id would have to be generated all over with rad init.

Troubleshooting

  • Bob’s rad clone fails with "no candidate seeds found".
    Garden hasn’t finished its initial sync from Alice yet, check the per-repo status on the Garden dashboard. The repo needs to be "synced" before Bob can fetch it.

  • Bob can fetch the identity doc but not Alice’s branch refs.
    Either Bob isn’t yet listed as a delegate (check rad inspect --identity on Alice’s side), or Garden hasn’t propagated the most recent identity update. Have Alice run rad sync again, then have Bob run rad sync --fetch --seed <garden-did> explicitly.

  • rad id update fails with "threshold cannot be met".
    This applies when raising the threshold to 2 (or more): Alice doesn’t yet have Bob’s signed refs locally. Run rad follow did:key:<bob-did> --alias bob followed by rad sync --fetch and try again.

  • Changes are not propagating between Alice and Bob.
    Confirm both sides have Garden in their preferredSeeds and in node.connect. Restart the local node if a session has gone stale, or run rad node connect <garden-connect-address> for an immediate reconnect.

An alternative: running Radicle behind Tor.

Radicle Garden isn’t the only way to deal with NAT. The Radicle user guide describes running a node behind Tor, which gives each participant a static .onion address that’s routable across the Tor network.

Peers behind NAT can then connect to each other directly via those .onion addresses, without needing a third party in the middle. Tor and Radicle Garden solve different parts of the same problem, and which one fits better depends on what you care about:

  • Tor solves NAT traversal and adds anonymity and censorship resistance, your node’s IP is hidden, and your repo is reachable from the open internet via its .onion address. There’s no third party involved. But the two peers still need to be online at the same time for refs to flow, since the only storage on the path is the laptops themselves. Some networks also block Tor.

  • Garden solves NAT traversal and availability, the seed is always-on, so Alice and Bob never need to be simultaneously online. The trade-off is the obvious one: there’s a third party in the loop, which you have to trust. To avoid trusting the Garden node, you can encrypt your repository contents, issues and patches using encryption. For example, see: git-remote-seal and rad-seal.

If Tor sounds like a better fit, the Radicle user guide is the right place to start.

Want to learn more about Radicle, the sovereign forge? Visit radicle.network
© 2026 The Radicle Team
Terms of Service Privacy Policy