configuration for self hosting a spindle in docker
Shell 61.6%
Dockerfile 22.3%
HCL 16.1%
13 1 0

Clone this repository

https://tangled.org/danieldaum.net/spindle-docker https://tangled.org/did:plc:be2e4qfe6docqcysyxxwvsor/spindle-docker
git@knot.danieldaum.net:danieldaum.net/spindle-docker git@knot.danieldaum.net:did:plc:be2e4qfe6docqcysyxxwvsor/spindle-docker

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

spindle-docker#

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.

Docker Compose stack for self-hosting a Tangled spindle (CI runner) with OpenBao for secrets management.

.
├── docker-compose.yml
├── Dockerfile
├── init-openbao.sh          # one-time vault bootstrap
└── config/openbao/
    ├── server/
    │   └── server.hcl       # OpenBao server config
    ├── proxy/
    │   └── proxy.hcl        # AppRole auto-auth proxy config
    └── spindle-policy.hcl   # KV access policy for spindle

Prerequisites#

  • Docker + Docker Compose
  • A domain or IP reachable by the Tangled network
  • Your ATProto DID

Configuration#

Docker Compose loads .env automatically. Copy the sample and fill in the two required values:

cp .env.sample .env
Variable Required Default Description
SPINDLE_SERVER_HOSTNAME yes Public hostname or IP (e.g. spindle.example.com)
SPINDLE_SERVER_OWNER yes Your ATProto DID (e.g. did:plc:xxxx)
SPINDLE_PORT no 6555 Host port Spindle is exposed on
OPENBAO_PORT no 8200 Host port OpenBao is exposed on (local CLI access)
SPINDLE_SERVER_LISTEN_ADDR no 0.0.0.0:6555 Bind address inside the container
SPINDLE_SERVER_DB_PATH no /data/spindle.db SQLite database path inside the container
SPINDLE_PIPELINES_LOG_DIR no /var/log/spindle Pipeline log directory inside the container
SPINDLE_SERVER_SECRETS_OPENBAO_MOUNT no spindle KV v2 mount name

First-time setup#

1. Configure environment

git clone https://tangled.org/danieldaum.net/spindle-docker
cd spindle-docker
cp .env.sample .env
# Edit .env — set SPINDLE_SERVER_HOSTNAME and SPINDLE_SERVER_OWNER

2. Start OpenBao

docker compose up -d openbao

Wait until you see the following line in the logs before continuing (docker compose logs -f openbao):

core: seal configuration missing, not initialized

3. Initialize the vault (once only)

chmod +x init-openbao.sh
./init-openbao.sh

The 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).

4. Start the full stack

Once the init script completes successfully:

docker compose up -d

After a restart#

OpenBao 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):

docker compose exec openbao bao operator unseal <unseal_key>

The proxy and Spindle will start automatically once OpenBao is unsealed and healthy.

Verify#

curl http://localhost:6555/   # Spindle (should return the spindle welcome page)

Architecture#

spindle (:6555) → openbao-proxy (:8201) → openbao (:8200)
spindle → /var/run/docker.sock  (pipeline containers run on the host daemon)
  • openbao — secrets vault; sealed on every start
  • openbao-proxy — AppRole sidecar; auto-authenticates and exposes a token-authenticated proxy to spindle
  • spindle — the CI runner; starts only after the proxy is healthy

Pinned versions#

All images and source are pinned to specific versions and verified by digest or commit SHA to prevent unexpected changes on rebuild.

Component Version Where
OpenBao 2.5.2 docker-compose.yml
Go (builder) 1.25.8-alpine3.23 Dockerfile
Alpine (runtime) 3.23.3 Dockerfile
Spindle source v1.13.0-alpha (c3f60dc1) Dockerfile

To 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.

Notes#

  • Port 8200 is exposed for local CLI access only (127.0.0.1). Remove that port mapping entirely if you don't need it.
  • TLS is disabled on both listeners. Put nginx or Caddy in front for production traffic.
  • Spindle mounts the Docker socket, so pipeline containers run on the host daemon.

AppRole credential handling#

By 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.

The 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.

If you want the secret-id deleted after first use (higher security, more operational overhead):

  1. In docker-compose.yml, remove :ro from the approle volume mount:

    - openbao-approle:/openbao/approle
    
  2. After any restart or proxy container recreation, generate and write a new secret-id before starting the proxy:

    # Unseal first, then:
    SECRET_ID=$(docker compose exec -T openbao bao write \
      -address=http://localhost:8200 -f -field=secret_id \
      auth/approle/role/spindle/secret-id)
    
    docker run --rm \
      -v "openbao-approle:/openbao/approle" \
      --entrypoint="" \
      alpine:3.23.3 \
      sh -c "printf '%s' '$SECRET_ID' > /openbao/approle/secret-id \
        && chown 100:1000 /openbao/approle/secret-id \
        && chmod 640 /openbao/approle/secret-id"
    
    docker compose restart openbao-proxy
    

This repository is hosted on Tangled and mirrored to GitHub.