configuration for self hosting a spindle in docker
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

initial commit

daniel daum 982cfcd9

+320
+25
Dockerfile.spindle
··· 1 + # ── Build stage ─────────────────────────────────────────────────────────────── 2 + FROM golang:1.23-alpine AS builder 3 + 4 + RUN apk add --no-cache git 5 + 6 + WORKDIR /src 7 + 8 + # Clone the core repo from tangled.org 9 + RUN git clone https://tangled.org/tangled.org/core . 10 + 11 + RUN go mod download 12 + RUN go build -o /spindle ./cmd/spindle/main.go 13 + 14 + # ── Runtime stage ───────────────────────────────────────────────────────────── 15 + FROM alpine:3.20 16 + 17 + RUN apk add --no-cache ca-certificates docker-cli 18 + 19 + COPY --from=builder /spindle /usr/local/bin/spindle 20 + 21 + RUN mkdir -p /data /var/log/spindle 22 + 23 + EXPOSE 6555 24 + 25 + ENTRYPOINT ["/usr/local/bin/spindle"]
+82
README.md
··· 1 + # tangled spindle + openbao 2 + 3 + ## Layout 4 + 5 + ``` 6 + . 7 + ├── docker-compose.yml 8 + ├── Dockerfile.spindle # clones & builds from tangled.org/core 9 + ├── .env.example # copy to .env and fill in 10 + ├── config/ 11 + │ └── openbao/ 12 + │ ├── server.hcl # production file-backend config 13 + │ ├── proxy.hcl # AppRole auto-auth proxy 14 + │ └── spindle-policy.hcl # KV policy for spindle 15 + └── scripts/ 16 + └── init-openbao.sh # one-time vault bootstrap 17 + ``` 18 + 19 + ## First-time setup 20 + 21 + ### 1. Configure spindle 22 + 23 + ```bash 24 + cp .env.example .env 25 + # edit .env — set SPINDLE_SERVER_HOSTNAME and SPINDLE_SERVER_OWNER 26 + ``` 27 + 28 + ### 2. Start OpenBao only 29 + 30 + ```bash 31 + docker compose up -d openbao 32 + ``` 33 + 34 + Wait for it to be healthy (~5 s), then: 35 + 36 + ### 3. Initialise the vault 37 + 38 + ```bash 39 + chmod +x scripts/init-openbao.sh 40 + ./scripts/init-openbao.sh 41 + ``` 42 + 43 + Save the unseal key and root token printed to stdout. 44 + 45 + ### 4. Start the rest of the stack 46 + 47 + ```bash 48 + docker compose up -d 49 + ``` 50 + 51 + Spindle comes up only after the proxy is healthy, which is after the vault 52 + is unsealed and AppRole credentials are in the shared volume. 53 + 54 + ## After a restart 55 + 56 + OpenBao is **sealed on every restart** — that's intentional for production. 57 + Unseal it before the proxy (and therefore spindle) can authenticate: 58 + 59 + ```bash 60 + docker compose exec openbao bao operator unseal http://localhost:8200 <unseal_key> 61 + ``` 62 + 63 + Or automate this with a secrets manager / HSM if you don't want manual steps. 64 + 65 + ## Verify 66 + 67 + ```bash 68 + # proxy health 69 + curl http://localhost:8201/v1/sys/health 70 + 71 + # spindle port 72 + curl http://localhost:6555/ 73 + ``` 74 + 75 + ## Notes 76 + 77 + - Spindle mounts `/var/run/docker.sock` so it can spawn pipeline containers 78 + on the **host** Docker daemon — make sure the spindle user has permission. 79 + - TLS is disabled on both listeners; put a reverse proxy (nginx/caddy) in 80 + front for production traffic. 81 + - The `openbao-approle` volume holds the role-id/secret-id written by the 82 + init script and mounted read-only by the proxy container.
+84
docker-compose.yml
··· 1 + services: 2 + 3 + # ── OpenBao server (production) ──────────────────────────────────────────── 4 + openbao: 5 + image: quay.io/openbao/openbao:latest 6 + container_name: openbao 7 + restart: unless-stopped 8 + cap_add: 9 + - IPC_LOCK # required to prevent secrets being swapped to disk 10 + command: server 11 + volumes: 12 + - ./config/openbao/server.hcl:/openbao/config/server.hcl:ro 13 + - openbao-data:/openbao/data 14 + ports: 15 + - "8200:8200" # expose only if you need CLI access from the host 16 + environment: 17 + BAO_ADDR: "http://0.0.0.0:8200" 18 + networks: 19 + - spindle-net 20 + healthcheck: 21 + test: ["CMD", "bao", "status", "-address=http://127.0.0.1:8200"] 22 + interval: 10s 23 + timeout: 5s 24 + retries: 5 25 + start_period: 5s 26 + 27 + # ── OpenBao proxy (AppRole auto-auth sidecar) ────────────────────────────── 28 + openbao-proxy: 29 + image: quay.io/openbao/openbao:latest 30 + container_name: openbao-proxy 31 + restart: unless-stopped 32 + command: proxy -config=/openbao/config/proxy.hcl 33 + depends_on: 34 + openbao: 35 + condition: service_healthy 36 + volumes: 37 + - ./config/openbao/proxy.hcl:/openbao/config/proxy.hcl:ro 38 + - openbao-approle:/openbao/approle:ro # role-id + secret-id written by init script 39 + networks: 40 + - spindle-net 41 + healthcheck: 42 + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8201/v1/sys/health"] 43 + interval: 10s 44 + timeout: 5s 45 + retries: 5 46 + start_period: 10s 47 + 48 + # ── Spindle (built from tangled.org/core) ────────────────────────────────── 49 + spindle: 50 + build: 51 + context: . 52 + dockerfile: Dockerfile.spindle 53 + container_name: spindle 54 + restart: unless-stopped 55 + depends_on: 56 + openbao-proxy: 57 + condition: service_healthy 58 + volumes: 59 + - /var/run/docker.sock:/var/run/docker.sock # spindle spawns pipeline containers 60 + - spindle-db:/data 61 + - spindle-logs:/var/log/spindle 62 + ports: 63 + - "6555:6555" 64 + env_file: 65 + - .env # SPINDLE_SERVER_HOSTNAME, SPINDLE_SERVER_OWNER 66 + environment: 67 + SPINDLE_SERVER_LISTEN_ADDR: "0.0.0.0:6555" 68 + SPINDLE_SERVER_DB_PATH: "/data/spindle.db" 69 + SPINDLE_SERVER_SECRETS_PROVIDER: "openbao" 70 + SPINDLE_SERVER_SECRETS_OPENBAO_PROXY_ADDR: "http://openbao-proxy:8201" 71 + SPINDLE_SERVER_SECRETS_OPENBAO_MOUNT: "spindle" 72 + SPINDLE_PIPELINES_LOG_DIR: "/var/log/spindle" 73 + networks: 74 + - spindle-net 75 + 76 + volumes: 77 + openbao-data: 78 + openbao-approle: 79 + spindle-db: 80 + spindle-logs: 81 + 82 + networks: 83 + spindle-net: 84 + driver: bridge
+67
init-openbao.sh
··· 1 + #!/usr/bin/env bash 2 + # Run this ONCE after first `docker compose up -d openbao`. 3 + # It initialises the vault, unseals it, and wires up AppRole for spindle. 4 + set -euo pipefail 5 + 6 + BAO="docker compose exec openbao bao" 7 + BAO_ADDR="http://localhost:8200" 8 + 9 + echo "==> Initialising OpenBao (1 key share, threshold 1)..." 10 + INIT_OUTPUT=$($BAO operator init \ 11 + -address="$BAO_ADDR" \ 12 + -key-shares=1 \ 13 + -key-threshold=1 \ 14 + -format=json) 15 + 16 + UNSEAL_KEY=$(echo "$INIT_OUTPUT" | grep -o '"unseal_keys_b64":\["[^"]*"' | grep -o '"[^"]*"$' | tr -d '"') 17 + ROOT_TOKEN=$(echo "$INIT_OUTPUT" | grep -o '"root_token":"[^"]*"' | cut -d'"' -f4) 18 + 19 + echo "" 20 + echo "┌─────────────────────────────────────────────────────────┐" 21 + echo "│ SAVE THESE — they will not be shown again │" 22 + echo "│ │" 23 + echo "│ Unseal key : $UNSEAL_KEY" 24 + echo "│ Root token : $ROOT_TOKEN" 25 + echo "└─────────────────────────────────────────────────────────┘" 26 + echo "" 27 + 28 + echo "==> Unsealing..." 29 + $BAO operator unseal -address="$BAO_ADDR" "$UNSEAL_KEY" 30 + 31 + echo "==> Logging in with root token..." 32 + $BAO login -address="$BAO_ADDR" "$ROOT_TOKEN" 33 + 34 + echo "==> Enabling KV v2 at path 'spindle'..." 35 + $BAO secrets enable -address="$BAO_ADDR" -path=spindle -version=2 kv 36 + 37 + echo "==> Writing spindle policy..." 38 + docker compose cp config/openbao/spindle-policy.hcl openbao:/tmp/spindle-policy.hcl 39 + $BAO policy write -address="$BAO_ADDR" spindle-policy /tmp/spindle-policy.hcl 40 + 41 + echo "==> Enabling AppRole auth..." 42 + $BAO auth enable -address="$BAO_ADDR" approle 43 + 44 + $BAO write -address="$BAO_ADDR" auth/approle/role/spindle \ 45 + token_policies="spindle-policy" \ 46 + token_ttl=1h \ 47 + token_max_ttl=4h \ 48 + bind_secret_id=true \ 49 + secret_id_ttl=0 \ 50 + secret_id_num_uses=0 51 + 52 + echo "==> Fetching AppRole credentials..." 53 + ROLE_ID=$($BAO read -address="$BAO_ADDR" -field=role_id auth/approle/role/spindle/role-id) 54 + SECRET_ID=$($BAO write -address="$BAO_ADDR" -f -field=secret_id auth/approle/role/spindle/secret-id) 55 + 56 + echo "==> Writing credentials into the openbao-approle volume..." 57 + # Write into a temp container that mounts the shared volume 58 + docker run --rm \ 59 + -v tangled-spindle_openbao-approle:/openbao/approle \ 60 + alpine sh -c " 61 + echo '$ROLE_ID' > /openbao/approle/role-id && chmod 600 /openbao/approle/role-id 62 + echo '$SECRET_ID' > /openbao/approle/secret-id && chmod 600 /openbao/approle/secret-id 63 + " 64 + 65 + echo "" 66 + echo "==> Done. Now start the full stack:" 67 + echo " docker compose up -d"
+35
proxy.hcl
··· 1 + vault { 2 + address = "http://openbao:8200" 3 + } 4 + 5 + auto_auth { 6 + method "approle" { 7 + mount_path = "auth/approle" 8 + config = { 9 + role_id_file_path = "/openbao/approle/role-id" 10 + secret_id_file_path = "/openbao/approle/secret-id" 11 + } 12 + } 13 + 14 + sink "file" { 15 + config = { 16 + path = "/tmp/openbao-token" 17 + mode = 0640 18 + } 19 + } 20 + } 21 + 22 + listener "tcp" { 23 + address = "0.0.0.0:8201" 24 + tls_disable = true 25 + } 26 + 27 + api_proxy { 28 + use_auto_auth_token = true 29 + } 30 + 31 + cache { 32 + use_auto_auth_token = true 33 + } 34 + 35 + log_level = "info"
+12
server.hcl
··· 1 + ui = false 2 + 3 + storage "file" { 4 + path = "/openbao/data" 5 + } 6 + 7 + listener "tcp" { 8 + address = "0.0.0.0:8200" 9 + tls_disable = true # put TLS termination at your reverse proxy 10 + } 11 + 12 + api_addr = "http://openbao:8200"
+15
spindle-policy.hcl
··· 1 + path "spindle/data/*" { 2 + capabilities = ["create", "read", "update", "delete"] 3 + } 4 + 5 + path "spindle/metadata/*" { 6 + capabilities = ["list", "read", "delete", "update"] 7 + } 8 + 9 + path "spindle/" { 10 + capabilities = ["list"] 11 + } 12 + 13 + path "auth/token/lookup-self" { 14 + capabilities = ["read"] 15 + }