···11---
22-title: Tangled Documentation
22+title: Tangled docs
33author: The Tangled Contributors
44date: 21 Sun, Dec 2025
55---
···8899Tangled is a decentralized code hosting and collaboration
1010platform. Every component of Tangled is open-source and
1111-selfhostable. [tangled.org](https://tangled.org) also
1111+self-hostable. [tangled.org](https://tangled.org) also
1212provides hosting and CI services that are free to use.
13131414There are several models for decentralized code
1515collaboration platforms, ranging from ActivityPub’s
1616(Forgejo) federated model, to Radicle’s entirely P2P model.
1717Our approach attempts to be the best of both worlds by
1818-adopting atproto—a protocol for building decentralized
1818+adopting the AT Protocol—a protocol for building decentralized
1919social applications with a central identity
20202121Our approach to this is the idea of “knots”. Knots are
···2626default, Tangled provides managed knots where you can host
2727your repositories for free.
28282929-The "appview" at tangled.org acts as a consolidated “view”
2929+The appview at tangled.org acts as a consolidated "view"
3030into the whole network, allowing users to access, clone and
3131contribute to repositories hosted across different knots
3232seamlessly.
33333434-# Quick Start Guide
3434+# Quick start guide
35353636-## Login or Sign up
3636+## Login or sign up
37373838-You can [login](https://tangled.org) by using your AT
3838+You can [login](https://tangled.org) by using your AT Protocol
3939account. If you are unclear on what that means, simply head
4040to the [signup](https://tangled.org/signup) page and create
4141an account. By doing so, you will be choosing Tangled as
4242your account provider (you will be granted a handle of the
4343form `user.tngl.sh`).
44444545-In the AT network, users are free to choose their account
4545+In the AT Protocol network, users are free to choose their account
4646provider (known as a "Personal Data Service", or PDS), and
4747login to applications that support AT accounts.
48484949-You can think of it as "one account for all of the
5050-atmosphere"!
4949+You can think of it as "one account for all of the atmosphere"!
51505251If you already have an AT account (you may have one if you
5352signed up to Bluesky, for example), you can login with the
5453same handle on Tangled (so just use `user.bsky.social` on
5554the login page).
56555757-## Add an SSH Key
5656+## Add an SSH key
58575958Once you are logged in, you can start creating repositories
6059and pushing code. Tangled supports pushing git repositories
···8786paste your public key, give it a descriptive name, and hit
8887save.
89889090-## Create a Repository
8989+## Create a repository
91909291Once your SSH key is added, create your first repository:
9392···98974. Choose a knotserver to host this repository on
99985. Hit create
10099101101-"Knots" are selfhostable, lightweight git servers that can
100100+Knots are self-hostable, lightweight Git servers that can
102101host your repository. Unlike traditional code forges, your
103102code can live on any server. Read the [Knots](TODO) section
104103for more.
···125124are hosted by tangled.org. If you use a custom knot, refer
126125to the [Knots](TODO) section.
127126128128-## Push Your First Repository
127127+## Push your first repository
129128130130-Initialize a new git repository:
129129+Initialize a new Git repository:
131130132131```bash
133132mkdir my-project
···165164cd /path/to/your/existing/repo
166165```
167166168168-You can inspect your existing git remote like so:
167167+You can inspect your existing Git remote like so:
169168170169```bash
171170git remote -v
···197196origin git@tangled.org:user.tngl.sh/my-project (push)
198197```
199198200200-Push all your branches and tags to tangled:
199199+Push all your branches and tags to Tangled:
201200202201```bash
203202git push -u origin --all
···232231```
233232234233You also need to re-add the original URL as a push
235235-destination (git replaces the push URL when you use `--add`
234234+destination (Git replaces the push URL when you use `--add`
236235the first time):
237236238237```bash
···249248```
250249251250Notice that there's one fetch URL (the primary remote) and
252252-two push URLs. Now, whenever you push, git will
251251+two push URLs. Now, whenever you push, Git will
253252automatically push to both remotes:
254253255254```bash
···301300## Docker
302301303302Refer to
304304-[@tangled.org/knot-docker](https://tangled.sh/@tangled.sh/knot-docker).
303303+[@tangled.org/knot-docker](https://tangled.org/@tangled.org/knot-docker).
305304Note that this is community maintained.
306305307306## Manual setup
···372371```
373372KNOT_REPO_SCAN_PATH=/home/git
374373KNOT_SERVER_HOSTNAME=knot.example.com
375375-APPVIEW_ENDPOINT=https://tangled.sh
374374+APPVIEW_ENDPOINT=https://tangled.org
376375KNOT_SERVER_OWNER=did:plc:foobar
377376KNOT_SERVER_INTERNAL_LISTEN_ADDR=127.0.0.1:5444
378377KNOT_SERVER_LISTEN_ADDR=127.0.0.1:5555
···603602- `nixery`: This uses an instance of
604603 [Nixery](https://nixery.dev) to run steps, which allows
605604 you to add [dependencies](#dependencies) from
606606- [Nixpkgs](https://github.com/NixOS/nixpkgs). You can
605605+ Nixpkgs (https://github.com/NixOS/nixpkgs). You can
607606 search for packages on https://search.nixos.org, and
608607 there's a pretty good chance the package(s) you're looking
609608 for will be there.
···630629 default, the depth is set to 1, meaning only the most
631630 recent commit will be fetched, which is the commit that
632631 triggered the workflow.
633633-- `submodules`: If you use [git
634634- submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules)
632632+- `submodules`: If you use Git submodules
633633+ (https://git-scm.com/book/en/v2/Git-Tools-Submodules)
635634 in your repository, setting this field to `true` will
636635 recursively fetch all submodules. This is `false` by
637636 default.
···657656Say you want to fetch Node.js and Go from `nixpkgs`, and a
658657package called `my_pkg` you've made from your own registry
659658at your repository at
660660-`https://tangled.sh/@example.com/my_pkg`. You can define
659659+`https://tangled.org/@example.com/my_pkg`. You can define
661660those dependencies like so:
662661663662```yaml
···779778780779If you want another example of a workflow, you can look at
781780the one [Tangled uses to build the
782782-project](https://tangled.sh/@tangled.sh/core/blob/master/.tangled/workflows/build.yml).
781781+project](https://tangled.org/@tangled.org/core/blob/master/.tangled/workflows/build.yml).
783782784783## Self-hosting guide
785784···836835837836## Architecture
838837839839-Spindle is a small CI runner service. Here's a high level overview of how it operates:
838838+Spindle is a small CI runner service. Here's a high-level overview of how it operates:
840839841841-* listens for [`sh.tangled.spindle.member`](/lexicons/spindle/member.json) and
840840+* Listens for [`sh.tangled.spindle.member`](/lexicons/spindle/member.json) and
842841[`sh.tangled.repo`](/lexicons/repo.json) records on the Jetstream.
843843-* when a new repo record comes through (typically when you add a spindle to a
842842+* When a new repo record comes through (typically when you add a spindle to a
844843repo from the settings), spindle then resolves the underlying knot and
845844subscribes to repo events (see:
846845[`sh.tangled.pipeline`](/lexicons/pipeline.json)).
847847-* the spindle engine then handles execution of the pipeline, with results and
848848-logs beamed on the spindle event stream over wss
846846+* The spindle engine then handles execution of the pipeline, with results and
847847+logs beamed on the spindle event stream over WebSocket
849848850849### The engine
851850852851At present, the only supported backend is Docker (and Podman, if Docker
853853-compatibility is enabled, so that `/run/docker.sock` is created). Spindle
852852+compatibility is enabled, so that `/run/docker.sock` is created). spindle
854853executes each step in the pipeline in a fresh container, with state persisted
855854across steps within the `/tangled/workspace` directory.
856855···862861863862## Secrets with openbao
864863865865-This document covers setting up Spindle to use OpenBao for secrets
864864+This document covers setting up spindle to use OpenBao for secrets
866865management via OpenBao Proxy instead of the default SQLite backend.
867866868867### Overview
869868870869Spindle now uses OpenBao Proxy for secrets management. The proxy handles
871871-authentication automatically using AppRole credentials, while Spindle
870870+authentication automatically using AppRole credentials, while spindle
872871connects to the local proxy instead of directly to the OpenBao server.
873872874873This approach provides better security, automatic token renewal, and
···876875877876### Installation
878877879879-Install OpenBao from nixpkgs:
878878+Install OpenBao from Nixpkgs:
880879881880```bash
882881nix shell nixpkgs#openbao # for a local server
···10291028 }
10301029}
1031103010321032-# Proxy listener for Spindle
10311031+# Proxy listener for spindle
10331032listener "tcp" {
10341033 address = "127.0.0.1:8201"
10351034 tls_disable = true
···1062106110631062#### Configure spindle
1064106310651065-Set these environment variables for Spindle:
10641064+Set these environment variables for spindle:
1066106510671066```bash
10681067export SPINDLE_SERVER_SECRETS_PROVIDER=openbao
···10701069export SPINDLE_SERVER_SECRETS_OPENBAO_MOUNT=spindle
10711070```
1072107110731073-On startup, the spindle will now connect to the local proxy,
10721072+On startup, spindle will now connect to the local proxy,
10741073which handles all authentication automatically.
1075107410761075### Production setup for proxy
···10991098# List all secrets
11001099bao kv list spindle/
1101110011021102-# Add a test secret via Spindle API, then check it exists
11011101+# Add a test secret via the spindle API, then check it exists
11031102bao kv list spindle/repos/
1104110311051104# Get a specific secret
···11121111 port 8200 or 8201)
11131112- The proxy authenticates with OpenBao using AppRole
11141113 credentials
11151115-- All Spindle requests go through the proxy, which injects
11141114+- All spindle requests go through the proxy, which injects
11161115 authentication tokens
11171116- Secrets are stored at
11181117 `spindle/repos/{sanitized_repo_path}/{secret_key}`
···11311130and the policy has the necessary permissions.
1132113111331132**404 route errors**: The spindle KV mount probably doesn't
11341134-exist - run the mount creation step again.
11331133+exist—run the mount creation step again.
1135113411361135**Proxy authentication failures**: Check the proxy logs and
11371136verify the role-id and secret-id files are readable and
···11591158 secret_id="$(cat /tmp/openbao/secret-id)"
11601159```
1161116011621162-# Migrating knots & spindles
11611161+# Migrating knots and spindles
1163116211641163Sometimes, non-backwards compatible changes are made to the
11651164knot/spindle XRPC APIs. If you host a knot or a spindle, you
···1172117111731172## Upgrading from v1.8.x
1174117311751175-After v1.8.2, the HTTP API for knot and spindles have been
11741174+After v1.8.2, the HTTP API for knots and spindles has been
11761175deprecated and replaced with XRPC. Repositories on outdated
11771176knots will not be viewable from the appview. Upgrading is
11781177straightforward however.
1179117811801179For knots:
1181118011821182-- Upgrade to latest tag (v1.9.0 or above)
11811181+- Upgrade to the latest tag (v1.9.0 or above)
11831182- Head to the [knot dashboard](https://tangled.org/settings/knots) and
11841183 hit the "retry" button to verify your knot
1185118411861185For spindles:
1187118611881188-- Upgrade to latest tag (v1.9.0 or above)
11871187+- Upgrade to the latest tag (v1.9.0 or above)
11891188- Head to the [spindle
11901189 dashboard](https://tangled.org/settings/spindles) and hit the
11911190 "retry" button to verify your spindle
···12271226# Hacking on Tangled
1228122712291228We highly recommend [installing
12301230-nix](https://nixos.org/download/) (the package manager)
12311231-before working on the codebase. The nix flake provides a lot
12291229+Nix](https://nixos.org/download/) (the package manager)
12301230+before working on the codebase. The Nix flake provides a lot
12321231of helpers to get started and most importantly, builds and
12331232dev shells are entirely deterministic.
12341233···12381237nix develop
12391238```
1240123912411241-Non-nix users can look at the `devShell` attribute in the
12401240+Non-Nix users can look at the `devShell` attribute in the
12421241`flake.nix` file to determine necessary dependencies.
1243124212441243## Running the appview
1245124412461246-The nix flake also exposes a few `app` attributes (run `nix
12451245+The Nix flake also exposes a few `app` attributes (run `nix
12471246flake show` to see a full list of what the flake provides),
12481247one of the apps runs the appview with the `air`
12491248live-reloader:
···12581257nix run .#watch-tailwind
12591258```
1260125912611261-To authenticate with the appview, you will need redis and
12621262-OAUTH JWKs to be setup:
12601260+To authenticate with the appview, you will need Redis and
12611261+OAuth JWKs to be set up:
1263126212641263```
12651265-# oauth jwks should already be setup by the nix devshell:
12641264+# OAuth JWKs should already be set up by the Nix devshell:
12661265echo $TANGLED_OAUTH_CLIENT_SECRET
12671266z42ty4RT1ovnTopY8B8ekz9NuziF2CuMkZ7rbRFpAR9jBqMc
12681267···12801279# the secret key from above
12811280export TANGLED_OAUTH_CLIENT_SECRET="z42tuP..."
1282128112831283-# run redis in at a new shell to store oauth sessions
12821282+# Run Redis in a new shell to store OAuth sessions
12841283redis-server
12851284```
1286128512871286## Running knots and spindles
1288128712891288An end-to-end knot setup requires setting up a machine with
12901290-`sshd`, `AuthorizedKeysCommand`, and git user, which is
12911291-quite cumbersome. So the nix flake provides a
12891289+`sshd`, `AuthorizedKeysCommand`, and a Git user, which is
12901290+quite cumbersome. So the Nix flake provides a
12921291`nixosConfiguration` to do so.
1293129212941293<details>
12951295- <summary><strong>MacOS users will have to setup a Nix Builder first</strong></summary>
12941294+ <summary><strong>macOS users will have to set up a Nix Builder first</strong></summary>
1296129512971296 In order to build Tangled's dev VM on macOS, you will
12981297 first need to set up a Linux Nix builder. The recommended
···13031302 you are using Apple Silicon).
1304130313051304 > IMPORTANT: You must build `darwin.linux-builder` somewhere other than inside
13061306- > the tangled repo so that it doesn't conflict with the other VM. For example,
13051305+ > the Tangled repo so that it doesn't conflict with the other VM. For example,
13071306 > you can do
13081307 >
13091308 > ```shell
···13161315 > avoid subtle problems.
1317131613181317 Alternatively, you can use any other method to set up a
13191319- Linux machine with `nix` installed that you can `sudo ssh`
13181318+ Linux machine with Nix installed that you can `sudo ssh`
13201319 into (in other words, root user on your Mac has to be able
13211320 to ssh into the Linux machine without entering a password)
13221321 and that has the same architecture as your Mac. See
···13471346with `ssh` exposed on port 2222.
1348134713491348Once the services are running, head to
13501350-http://localhost:3000/settings/knots and hit verify. It should
13491349+http://localhost:3000/settings/knots and hit "Verify". It should
13511350verify the ownership of the services instantly if everything
13521351went smoothly.
13531352···1371137013721371The above VM should already be running a spindle on
13731372`localhost:6555`. Head to http://localhost:3000/settings/spindles and
13741374-hit verify. You can then configure each repository to use
13731373+hit "Verify". You can then configure each repository to use
13751374this spindle and run CI jobs.
1376137513771376Of interest when debugging spindles:
1378137713791378```
13801380-# service logs from journald:
13791379+# Service logs from journald:
13811380journalctl -xeu spindle
1382138113831382# CI job logs from disk:
13841383ls /var/log/spindle
1385138413861386-# debugging spindle db:
13851385+# Debugging spindle database:
13871386sqlite3 /var/lib/spindle/spindle.db
1388138713891388# litecli has a nicer REPL interface:
···1432143114331432### General notes
1434143314351435-- PRs get merged "as-is" (fast-forward) -- like applying a patch-series
14361436-using `git am`. At present, there is no squashing -- so please author
14341434+- PRs get merged "as-is" (fast-forward)—like applying a patch-series
14351435+using `git am`. At present, there is no squashing—so please author
14371436your commits as they would appear on `master`, following the above
14381437guidelines.
14391438- If there is a lot of nesting, for example "appview:
···14541453## Code formatting
1455145414561455We use a variety of tools to format our code, and multiplex them with
14571457-[`treefmt`](https://treefmt.com): all you need to do to format your changes
14561456+[`treefmt`](https://treefmt.com). All you need to do to format your changes
14581457is run `nix run .#fmt` (or just `treefmt` if you're in the devshell).
1459145814601459## Proposals for bigger changes
···14821481We'll use the issue thread to discuss and refine the idea before moving
14831482forward.
1484148314851485-## Developer certificate of origin (DCO)
14841484+## Developer Certificate of Origin (DCO)
1486148514871486We require all contributors to certify that they have the right to
14881487submit the code they're contributing. To do this, we follow the