configuration for self hosting a spindle in docker
CLAUDE.md#
This file provides guidance to Claude Code when working with code in this repository.
What this repo is#
A Docker Compose deployment stack for Spindle (a CI/CD pipeline tool) backed by OpenBao (an open-source HashiCorp Vault fork) for secrets management. Spindle is not developed here — it is cloned from tangled.org/tangled.org/core at tag v1.13.0-alpha and built inside Dockerfile.
Current state#
This stack is actively being tested for first-time deployment on Ubuntu Linux. The init script and Docker config have had several fixes applied and may need further iteration.
Known working:
- OpenBao starts and reads config correctly
init-openbao.shfixes data volume permissions, restarts OpenBao, and runs the full vault bootstrap
Known issues / things being tested:
- Full end-to-end stack (openbao → openbao-proxy → spindle) not yet verified
- Spindle healthcheck is intentionally omitted pending manual testing of its HTTP endpoints
Architecture#
┌──────────┐ AppRole ┌───────────────┐ KV v2 ┌─────────┐
│ spindle │ ─────────────► │ openbao-proxy │ ─────────────► │ openbao │
│ :6555 │ │ :8201 │ │ :8200 │
└──────────┘ └───────────────┘ └─────────┘
│
│ /var/run/docker.sock
▼
Host Docker daemon (pipeline containers run here)
- openbao — vault backend, file storage, sealed on every start
- openbao-proxy — AppRole sidecar, auto-authenticates, token cached at
/tmp/openbao-token - spindle — CI runner, starts only after proxy is healthy, mounts Docker socket
Key config files#
| File | Purpose |
|---|---|
docker-compose.yml |
Service definitions, volumes, port bindings, dependency order |
Dockerfile |
Clones tangled.org/core at v1.13.0-alpha, builds with Go, produces Alpine image |
config/openbao/server/server.hcl |
OpenBao server (file storage, TCP listener on 8200, TLS off) |
config/openbao/proxy/proxy.hcl |
Proxy AppRole auto-auth, token sink at /tmp/openbao-token, listener on 8201 |
config/openbao/spindle-policy.hcl |
Grants Spindle KV v2 CRUD on spindle/data/* and spindle/metadata/* |
init-openbao.sh |
One-time bootstrap: fixes volume perms, inits vault, enables KV v2, creates AppRole |
.env.sample |
All configurable env vars with defaults — copy to .env before starting |
Important implementation details#
- Config directories are split:
config/openbao/server/andconfig/openbao/proxy/are mounted separately so each service only sees its own HCL files. Mixing them caused OpenBao server to fail parsing proxy-specific stanzas. - The
openbao-dataDocker volume is created root-owned by default.init-openbao.shfixes this withdocker compose exec --user root openbao chown -R openbao:openbao /openbao/databefore running init. - All
docker compose execcalls in the init script use-Twith</dev/nullto prevent stdin hanging. - OpenBao port 8200 is bound to
127.0.0.1only (not exposed to the network). - Both OpenBao and its proxy have
IPC_LOCKcapability to prevent secrets swapping to disk. - All images are pinned to SHA256 digests. Spindle source is pinned to commit
3572988b89fa093269ae78e02d7283ee206b6888.
First-time setup#
cp .env.sample .env
# Set SPINDLE_SERVER_HOSTNAME and SPINDLE_SERVER_OWNER in .env
docker compose up -d openbao
# Wait for "seal configuration missing, not initialized" in logs, then:
./init-openbao.sh # ONE-TIME ONLY — save the unseal key and root token it prints
docker compose up -d
After every restart#
OpenBao seals itself on restart. Unseal before the proxy or Spindle can start:
docker compose exec openbao bao operator unseal -address=http://localhost:8200 <unseal_key>
Verify the stack#
curl http://localhost:8200/v1/sys/health # OpenBao server (127.0.0.1 only)
curl http://localhost:6555/ # Spindle