configuration for self hosting a spindle in docker
1# spindle-docker
2
3> **Early development / personal project** — This stack was built for personal use and tested on Ubuntu Linux. It has not been tested across a wide range of environments and may have rough edges or undocumented assumptions. Use it at your own risk.
4
5Docker Compose stack for self-hosting a [Tangled](https://tangled.org) spindle (CI runner) with [OpenBao](https://openbao.org) for secrets management.
6
7```
8.
9├── docker-compose.yml
10├── Dockerfile
11├── init-openbao.sh # one-time vault bootstrap
12└── config/openbao/
13 ├── server/
14 │ └── server.hcl # OpenBao server config
15 ├── proxy/
16 │ └── proxy.hcl # AppRole auto-auth proxy config
17 └── spindle-policy.hcl # KV access policy for spindle
18```
19
20## Prerequisites
21
22- Docker + Docker Compose
23- A domain or IP reachable by the Tangled network
24- Your ATProto DID
25
26## Configuration
27
28Docker Compose loads `.env` automatically. Copy the sample and fill in the two required values:
29
30```bash
31cp .env.sample .env
32```
33
34| Variable | Required | Default | Description |
35| -------------------------------------- | -------- | ------------------ | -------------------------------------------------- |
36| `SPINDLE_SERVER_HOSTNAME` | yes | — | Public hostname or IP (e.g. `spindle.example.com`) |
37| `SPINDLE_SERVER_OWNER` | yes | — | Your ATProto DID (e.g. `did:plc:xxxx`) |
38| `SPINDLE_PORT` | no | `6555` | Host port Spindle is exposed on |
39| `OPENBAO_PORT` | no | `8200` | Host port OpenBao is exposed on (local CLI access) |
40| `SPINDLE_SERVER_LISTEN_ADDR` | no | `0.0.0.0:6555` | Bind address inside the container |
41| `SPINDLE_SERVER_DB_PATH` | no | `/data/spindle.db` | SQLite database path inside the container |
42| `SPINDLE_PIPELINES_LOG_DIR` | no | `/var/log/spindle` | Pipeline log directory inside the container |
43| `SPINDLE_SERVER_SECRETS_OPENBAO_MOUNT` | no | `spindle` | KV v2 mount name |
44
45## First-time setup
46
47**1. Configure environment**
48
49```bash
50git clone https://tangled.org/danieldaum.net/spindle-docker
51cd spindle-docker
52cp .env.sample .env
53# Edit .env — set SPINDLE_SERVER_HOSTNAME and SPINDLE_SERVER_OWNER
54```
55
56**2. Start OpenBao**
57
58```bash
59docker compose up -d openbao
60```
61
62Wait until you see the following line in the logs before continuing (`docker compose logs -f openbao`):
63
64```
65core: seal configuration missing, not initialized
66```
67
68**3. Initialize the vault** (once only)
69
70```bash
71chmod +x init-openbao.sh
72./init-openbao.sh
73```
74
75The script fixes permissions, initialises the vault, and configures AppRole automatically. It will print an **unseal key** and **root token** — save both somewhere safe, they are not stored anywhere and cannot be recovered. You will also be prompted to choose a Secret ID TTL (press enter for no expiry).
76
77**4. Start the full stack**
78
79Once the init script completes successfully:
80
81```bash
82docker compose up -d
83```
84
85## After a restart
86
87OpenBao seals itself on every restart. Run the unseal command once OpenBao is running (you can confirm it's ready when `docker compose logs openbao` shows `core: seal configuration missing, not initialized` or the container is healthy):
88
89```bash
90docker compose exec openbao bao operator unseal <unseal_key>
91```
92
93The proxy and Spindle will start automatically once OpenBao is unsealed and healthy.
94
95## Verify
96
97```bash
98curl http://localhost:6555/ # Spindle (should return the spindle welcome page)
99```
100
101## Architecture
102
103```
104spindle (:6555) → openbao-proxy (:8201) → openbao (:8200)
105spindle → /var/run/docker.sock (pipeline containers run on the host daemon)
106```
107
108- **openbao** — secrets vault; sealed on every start
109- **openbao-proxy** — AppRole sidecar; auto-authenticates and exposes a token-authenticated proxy to spindle
110- **spindle** — the CI runner; starts only after the proxy is healthy
111
112## Pinned versions
113
114All images and source are pinned to specific versions and verified by digest or commit SHA to prevent unexpected changes on rebuild.
115
116| Component | Version | Where |
117| ---------------- | ---------------------------- | -------------------- |
118| OpenBao | `2.5.2` | `docker-compose.yml` |
119| Go (builder) | `1.25.8-alpine3.23` | `Dockerfile` |
120| Alpine (runtime) | `3.23.3` | `Dockerfile` |
121| Spindle source | `v1.13.0-alpha` (`c3f60dc1`) | `Dockerfile` |
122
123To upgrade any component, update the tag/version and its corresponding `@sha256:...` digest (or commit SHA for Spindle). All versions are currently alpha — there are no stable Spindle releases yet.
124
125## Notes
126
127- Port 8200 is exposed for local CLI access only (`127.0.0.1`). Remove that port mapping entirely if you don't need it.
128- TLS is disabled on both listeners. Put nginx or Caddy in front for production traffic.
129- Spindle mounts the Docker socket, so pipeline containers run on the **host** daemon.
130
131## AppRole credential handling
132
133By default, the `openbao-approle` volume is mounted read-only (`:ro`) in the proxy container. This means the proxy can read the `role-id` and `secret-id` written by `init-openbao.sh` on every startup, but cannot delete them. The credentials persist on the volume indefinitely, so the proxy can re-authenticate automatically after any restart with no user intervention beyond unsealing OpenBao.
134
135The tradeoff: the `secret-id` is never rotated or deleted. For a self-hosted server where Docker volumes are only accessible to the server owner, this is a reasonable default.
136
137**If you want the secret-id deleted after first use** (higher security, more operational overhead):
138
1391. In `docker-compose.yml`, remove `:ro` from the approle volume mount:
140
141 ```yaml
142 - openbao-approle:/openbao/approle
143 ```
144
1452. After any restart or proxy container recreation, generate and write a new secret-id before starting the proxy:
146
147 ```bash
148 # Unseal first, then:
149 SECRET_ID=$(docker compose exec -T openbao bao write \
150 -address=http://localhost:8200 -f -field=secret_id \
151 auth/approle/role/spindle/secret-id)
152
153 docker run --rm \
154 -v "openbao-approle:/openbao/approle" \
155 --entrypoint="" \
156 alpine:3.23.3 \
157 sh -c "printf '%s' '$SECRET_ID' > /openbao/approle/secret-id \
158 && chown 100:1000 /openbao/approle/secret-id \
159 && chmod 640 /openbao/approle/secret-id"
160
161 docker compose restart openbao-proxy
162 ```
163
164---
165
166This repository is hosted on [Tangled](https://tangled.org/danieldaum.net/spindle-docker) and mirrored to [GitHub](https://github.com/daniel-daum/spindle-docker).