Skip to main content
Use this workflow when you are iterating on taco/cmd/statesman and want faster rebuilds.
make -C self-hosting/docker-compose all-up
docker compose -f self-hosting/docker-compose/docker-compose.yml stop statesman
Run make -C ... from repo root, or run make all-up from self-hosting/docker-compose. If UI stays in Docker, point UI to host Statesman:
  • STATESMAN_BACKEND_URL=http://host.docker.internal:8080
If UI runs on host, set STATESMAN_BACKEND_URL=http://localhost:8080 in ui/.env.local.

Start Statesman from source

From taco/, run with the same shared secret used by UI:
export OPENTACO_PORT=8080
export OPENTACO_ENABLE_INTERNAL_ENDPOINTS=statesman-secret
export OPENTACO_STORAGE=memory
export OPENTACO_AUTH_DISABLE=true

go run ./cmd/statesman
For persisted/queryable data, switch to Postgres-backed settings used by Compose.

Sync organization and user for UI flows

Statesman resolves tenants by external_org_id (WorkOS org id). If missing, UI unit queries fail.
SECRET=$OPENTACO_ENABLE_INTERNAL_ENDPOINTS
ORG_ID=org_xxx
ORG_NAME=digger-org
ORG_DISPLAY="Digger Org"
USER_ID=user_xxx
USER_EMAIL=you@example.com

curl -X POST http://localhost:8080/internal/api/orgs/sync \
  -H "Authorization: Bearer $SECRET" \
  -H "X-User-ID: $USER_ID" \
  -H "X-Email: $USER_EMAIL" \
  -H "Content-Type: application/json" \
  -d '{"name":"'"$ORG_NAME"'","display_name":"'"$ORG_DISPLAY"'","external_org_id":"'"$ORG_ID"'"}'

curl -X POST http://localhost:8080/internal/api/users \
  -H "Authorization: Bearer $SECRET" \
  -H "X-Org-ID: $ORG_ID" \
  -H "X-User-ID: $USER_ID" \
  -H "X-Email: $USER_EMAIL" \
  -H "Content-Type: application/json" \
  -d '{"subject":"'"$USER_ID"'","email":"'"$USER_EMAIL"'"}'

Common issues

  • 403 from /internal/api/*: OPENTACO_ENABLE_INTERNAL_ENDPOINTS does not match UI STATESMAN_BACKEND_WEBHOOK_SECRET.
  • 404/500 when resolving organization: org not synced with external_org_id.
  • UI can sign in but cannot load units: user not synced into the same org.