···1+---
2+atroot: true
3+template:
4+slug: ci
5+title: introducing spindle
6+subtitle: tangled's new CI runner is now generally available
7+date: 2025-08-06
8+draft: true
9+authors:
10+ - name: Anirudh
11+ email: anirudh@tangled.sh
12+ handle: icyphox.sh
13+---
14+15+Since launching Tangled, continuous integration has consistently topped our
16+feature request list. And rightfully so -- modern software development is
17+unthinkable without automated testing, building, and deployment pipelines.
18+Today, we're excited to announce that CI is no longer a wishlist item, but a
19+fully-featured reality.
20+21+Meet **spindle**: Tangled's new CI runner that brings powerful automation
22+directly to your repositories. In typical Tangled fashion we've been dogfooding
23+spindle for a while now; this very blog post you're reading was [built and
24+published using spindle](link to CI run here).
25+26+
27+28+## how spindle works
29+30+Spindle is designed around simplicity and the decentralized nature of the AT
31+Protocol. Here's the flow: when you push code or open a pull request, the knot
32+hosting your repository emits a pipeline event (`sh.tangled.pipeline`). Running
33+as a dedicated service, spindle subscribes to these events via websocket
34+connections to your knot.
35+36+Once triggered, spindle reads your pipeline manifest, spins up the necessary
37+execution environment (covered below), and runs your defined workflow steps.
38+Throughout execution, it streams real-time logs and status updates
39+(`sh.tangled.pipeline.status`) back through websocktes, which the Tangled
40+appview subscribes to for live updates.
41+42+This architecture keeps everything responsive and real-time while maintaining
43+the distributed spirit that makes Tangled unique.
44+45+## spindle pipelines
46+47+The pipeline manifest is defined in YAML, and should be relatively familiar to
48+those that have used other CI products. Here's a minimal example:
49+50+```yaml
51+# test.yaml
52+53+when:
54+ - event: ["push", "pull_request"]
55+ branch: ["master"]
56+57+dependencies:
58+ nixpkgs:
59+ - go
60+61+steps:
62+ - name: patch static dir
63+ command: |
64+ mkdir -p appview/pages/static; touch appview/pages/static/x
65+66+ - name: run all tests
67+ environment:
68+ CGO_ENABLED: 1
69+ command: |
70+ go test -v ./...
71+```
72+73+Manifests are stored under your repo's `.tangled/workflows` directory. There may
74+be multiple manifests here describing different workflows -- for example, a
75+`build.yaml`, `test.yaml` and a `lint.yaml`. You can read the [full manifest spec
76+here](https://tangled.sh/@tangled.sh/core/blob/master/docs/spindle/pipeline.md).
77+78+The `when` block defines the set of events that can trigger the workflow run.
79+The above example will run tests on any push or pull request targeting the
80+`master` branch.
81+82+Now the `dependencies` block is the real interesting bit. Dependencies for your
83+workflow, like Go, Node.js, Python etc. can be pulled in from nixpkgs. nixpkgs
84+-- for the uninitiated -- is a vast collection of packages for the Nix package
85+manager. Fortunately, you needn't know nor care about Nix to use it! Just head
86+to https://search.nixos.org to find your package of choice (I'll bet 1€ that
87+it's there[^1]), toss it in the list and run your build. The Nix-savvy of you
88+lot will be happy to know that you can toss in custom registries there.
89+90+[^1]: I mean, if it isn't there, it's nowhere.
91+92+Finally, define your steps, neccesary environment variables and commands.
93+Commands can be multi-lined. Let's take a look at how spindle executes workflow
94+steps.
95+96+## workflow execution
97+98+At present, the spindle "engine" supports just the Docker backend[^2]. Podman is
99+known to work with the Docker socket feature enabled. Each step is run in a
100+separate container, with the `/tangled/workspace` and `/nix` volumes persisted
101+across steps.
102+103+[^2]: Support for additional backends like Firecracker are planned.
104+Contributions welcome!
105+106+The container image is built using [Nixery](https://nixery.dev). Nixery is a
107+nifty little tool that takes a path-separated set of Nix packages and returns an
108+OCI image with each package in a separate layer. Try this in your terminal if
109+you've got Docker installed:
110+111+```
112+docker run nixery.dev/bash/hello-go hello
113+```
114+115+This should output `Hello, world!`. This is running the
116+[hello-go](https://search.nixos.org/packages?channel=25.05&show=hello-go)
117+package from nixpkgs.
118+119+Nixery is super handy since we can construct these images for CI environments on
120+the fly, with all dependencies baked in, and the best part: caching for commonly
121+used packages is free thanks to Docker (pre-existing layers get reused). We run
122+a Nixery instance of our own at https://nixery.tangled.sh but you may override
123+that if you choose not to trust us.
124+125+## pipeline statuses and log streaming
126+127+Now that your workflow is running, watching it run to completion is half the
128+fun! Or watching it fail inexplicably for the hundredth time... which is
129+decidedly unfun. In any case, logs and pipeline statuses are streamed websockets
130+exposed by spindle, and show up in the brand new "pipelines" tab in your
131+repository.
132+133+
134+135+## pipeline secrets
136+137+Secrets are a bit tricky since atproto has no notion of private data. Secrets
138+are instead written directly from the appview to the spindle instance using
139+[service
140+auth](https://docs.bsky.app/docs/api/com-atproto-server-get-service-auth). In
141+essence, the appview makes a signed request using the logged-in user's DID key;
142+spindle verifies this signature by fetching the public key from the DID
143+document.
144+145+
146+147+The secrets themselves are stored in a configured secret manager. By default,
148+this is the same sqlite database that spindle uses. This is *fine* for
149+self-hosters. The hosted, flagship instance at https://spindle.tangled.sh
150+however uses [OpenBao](https://openbao.org), an OSS fork of HashiCorp Vault.
151+152+## get started now
153+154+You can run your own spindle instance pretty easily: the [spindle self-hosting
155+guide](https://tangled.sh/@tangled.sh/core/blob/master/docs/spindle/hosting.md)
156+should have you covered. Once done, head to your repository's settings tab and
157+set it up! Doesn't work? Feel free to pop into
158+[Discord](https://chat.tangled.sh) to get help -- we have a nice little crew
159+that's always around to help.
160+161+All Tangled users have access to our hosted spindle instance, free of
162+charge[^3]. You don't have any more excuses to not migrate to Tangled now --
163+[get started](https://tangled.sh/login) with your AT Protocol account today.
164+165+[^3]: We can't promise we won't charge for it at some point but there will
166+ always be a free tier.