# Self-hosting

> How a spindle runs and how to configure one, from a single NixOS host to a mill with remote executors.

A spindle is Tangled’s CI runner. It watches the knots of the repositories it serves, runs their `.tangled/workflows/*.yml` in microVMs or containers, and serves status and logs over XRPC. [Run a spindle](/docs/self-hosting/spindle/) is the short version of this page.

## Signing key

The spindle signs with an EC P-256 key that it publishes in its DID document. It never generates one, so make it once and point `SPINDLE_SERVER_KEY_PATH` (NixOS: `keyPath`) at it:

```sh
openssl ecparam -name prime256v1 -genkey -noout -out /var/lib/secrets/spindle-key.pem
```

## Requirements

* Linux with KVM for the `microvm` engine, or Docker for the `nixery` engine.
* A recent `git`.
* A public hostname with TLS.

## Install on NixOS

Import `nixosModules.spindle` from the Tangled flake:

```nix
{
  imports = [ tangled.nixosModules.spindle ];


  services.tangled.spindle = {
    enable = true;
    server = {
      hostname = "spindle.example.com";
      owner = "did:plc:yourdid";
    };
    keyPath = "/var/lib/secrets/spindle-key.pem";
    contact.email = "admin@spindle.example.com";
    artifactStores.s3.bucket = "";
  };


  systemd.tmpfiles.rules = [
    "L+ /var/lib/spindle/images/nixos - - - - ${tangled.packages.x86_64-linux.spindle-nixos-image}"
  ];
}
```

The module enables the `vhost`, `vsock` and `vhost_vsock` kernel modules and puts `qemu`, `e2fsprogs`, `slirp4netns`, `iproute2` and `util-linux` on the service’s `PATH`.

Put it behind an HTTPS reverse proxy to port 6555 that passes websocket upgrades and sends CORS headers. State lives in `/var/lib/spindle`, and secrets such as AWS credentials go in `environmentFile`. [Run a spindle](/docs/self-hosting/spindle/) walks through a first install.

**Elsewhere.** Build with `nix build .#spindle`, set the environment, and run `spindle`. There is no container image.

## MicroVM images

Workflows pick an image by name, for example `image: nixos`. Spindle looks for it in `SPINDLE_MICROVM_PIPELINES_IMAGE_DIR`. The flake builds `spindle-nixos-image`, `spindle-alpine-image` and `spindle-almalinux10-image`.

## Admins, admission and repositories

Admins are the owner plus `SPINDLE_SERVER_ADMINS`. With `SPINDLE_SERVER_ADMISSION=closed` (the default) the spindle serves only admins and its allow-list; with `open` it serves anyone who isn’t banned.

```sh
spindle allowlist add friend.example.com
spindle invite
```

A repository is served when its manifest names this spindle and its owner is admitted.

## Secrets

Repository secrets live in `spindle.db` (the default) or in OpenBao (`SPINDLE_SERVER_SECRETS_PROVIDER=openbao`). They are masked in logs and withheld from fork runs.

## Logs and artifacts

Logs are written to `SPINDLE_SERVER_LOG_DIR`, then archived to a disk directory or an S3 bucket. The NixOS module defaults to the `tangled-logs` bucket; set `artifactStores.s3.bucket = ""` to keep logs on disk.

## CI logs over SSH

Run a `cilogs` server (`nixosModules.cilogs`) and set `SPINDLE_SERVER_LOGS_ADDR` to its `host:port`. Pushes then print an `ssh` command that follows the run.

## Caches

* **Nix binary cache:** microVM guests read from `SPINDLE_NIX_CACHE_READ_URLS` and upload to `SPINDLE_NIX_CACHE_UPLOAD_URL`.
* **CI cache:** backs the workflow [`cache`](/docs/spindles/workflows/#cache) field. Set `SPINDLE_CACHE_BACKEND` to `disk` or `s3`.

## Quotas

`SPINDLE_QUOTA_USER_*` and `SPINDLE_QUOTA_REPO_*` cap workflows, CPU, memory, disk, cache and CI minutes. Override them per user:

```sh
spindle quota set friend.example.com --resource workflows --limit 4
spindle quota usage
spindle ban did:plc:spammer
```

## OIDC tokens for workflows

Set `SPINDLE_OIDC_ISSUER` and `SPINDLE_OIDC_KEY_PATH` (an EC P-256 key) to give trusted workflows an OIDC token for their `audience:`.

## Mill and executors

A spindle can split into a **mill**, which places jobs, and **executors**, which run them. Set `SPINDLE_ROLE` to `mill` or `executor`, and register each executor on the mill:

```sh
spindle mill executor token generate --db /var/lib/spindle/spindle.db > exec-a.token
spindle mill executor add --db /var/lib/spindle/spindle.db --token-file exec-a.token --label kvm exec-a
```

The mill and executors must share one artifact store.

## Troubleshooting

* Pushes never trigger anything: check that the owner is admitted and not banned.
* A workflow fails with a missing tool or device: install what the error names, see Requirements.
* Runs are slow to start: check `SPINDLE_SERVER_MAX_JOB_COUNT` and `spindle quota usage`.

[Spindle Configuration](/docs/reference/spindle-config/) lists every setting.