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.
- The scenario
- How it works at a glance
- Prerequisites
- Step 1 — Alice creates the repo
- Step 2 — Alice allow-lists Garden, then tells Garden to seed
- Step 3 — Alice adds Bob as a delegate
- Step 4 — Bob configures Garden and clones
- Step 5 — Bob makes his first commit
- Daily flow
- Optional: Adding more collaborators
- Troubleshooting
- An alternative: running Radicle behind Tor.
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:
-
Garden’s seed Decentralised IDentifier (DID) is in the repository’s
allowlist, so Garden is permitted to fetch. -
Both Alice and Bob are either delegates or in the
allowlist. -
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 radto publish your refs through Garden. -
rad sync --fetchto 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 clonefails 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 (checkrad inspect --identityon Alice’s side), or Garden hasn’t propagated the most recent identity update. Have Alice runrad syncagain, then have Bob runrad sync --fetch --seed <garden-did>explicitly. -
rad id updatefails 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. Runrad follow did:key:<bob-did> --alias bobfollowed byrad sync --fetchand try again. -
Changes are not propagating between Alice and Bob.
Confirm both sides have Garden in theirpreferredSeedsand innode.connect. Restart the local node if a session has gone stale, or runrad 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
.onionaddress. 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.