Operations

The reference for running ContextBay day-to-day: make targets, volume protocol, auth recovery, log locations, and the one-liners you reach for when something is on fire.

Make targets

The repo's top-level Makefile is the canonical interface for deploys, builds, and the pre-commit gate.

Deploy

TargetWhat it does
make deployBuild the master+worker images, recreate the local contextbay container with preserved env, and wait for /api/health to come back green. Use this after every code change.
make redeployAlias for make deploy. Same operation, easier to type after a code change.
make deploy-worker WORKER=hostname [SSH_USER=user]Build the worker binary locally and scp it to /opt/contextbay-worker/ on the remote, then restart the systemd unit or docker-image worker. Auto-detects mode; refuses on dual-mode hosts unless MODE_OVERRIDE=systemd or MODE_OVERRIDE=docker-image is supplied.

Build

TargetWhat it does
make buildBuild all three Go binaries (master + worker + cli) into dist/.
make build-masterMaster only. CGO_ENABLED=0 with version + commit + date ldflags.
make build-workerWorker only.
make build-cliCLI only. The contextbay-cli binary used by integration tests and scripted ops.
make build-webBuild the Next.js frontend and emit static files for go-embed.
make dockerBuild local Docker images for the master + worker (does not push).

Test + ready gate

TargetWhat it does
make testGo tests with race detector across cmd/... and internal/....
make test-integrationGo integration suites under tests/integration/.... Hits a real Docker daemon; needs sockets.
make test-webVitest run for the Next.js frontend.
make test-agentJest run for the Claude Agent SDK service in agent-service/.
make test-e2ePlaywright end-to-end tests against a running master.
make readyPre-commit gate: fmt + lint + test + test-integration + test-web + test-agent + build + typecheck. If this passes, your branch is ready to push.

Quality

TargetWhat it does
make fmtgofmt across cmd + internal.
make vetgo vet across master + worker code.
make lintgolangci-lint if installed; otherwise no-op (CI is local now).
make typecheckTypeScript strict typecheck for the frontend (tsc --noEmit).
make generateRun all go generate (mockgen for store + backend).
make protobuf generate in proto/.
make cleanRemove dist/, web/out, web/.next.

Volume protocol

ContextBay's state lives in a small set of Docker volumes that depend on each other. Understanding the chain is critical — wiping any one in isolation crash-loops the master.

The three core volumes

VolumeHoldsDepends on
contextbay-dataCB SQLite DB, generated configs, JWT secret, n8n encryption key, knowledge vault, mesh state.root of trust
contextbay-portainer-dataPortainer BoltDB, admin password, JWT.contextbay-data
cb-n8n_contextbay-n8n-datan8n SQLite + credentials encrypted with the persisted key.contextbay-data

The cb-* sub-container volumes

Each Portainer-deployed sub-container has its own volume(s) — for example cb-prometheus_data, cb-grafana_data, cb-loki_data, cb-headscale_data, cb-ollama_models. They are recreated automatically by the master's deploy loop, but they accumulate state (Prometheus history, Loki logs, Ollama model cache).

Wipe-and-redeploy fresh

To start completely fresh — for example to re-run the first-boot flow from scratch — stop the master, remove all three core volumes and the cb-* volumes, then redeploy:

docker stop contextbay
docker rm contextbay

# Remove the three core volumes (always together!)
docker volume rm contextbay-data contextbay-portainer-data \
  cb-n8n_contextbay-n8n-data

# Remove every cb-* volume
docker volume ls -q | grep '^cb-' | xargs -r docker volume rm

make deploy

Removing only one of the core volumes leaves the other two with stale credentials (Portainer's JWT no longer matches the new admin password; n8n's credentials can't be decrypted with the new encryption key). The master will detect this on startup and crash-loop until you wipe the rest.

Auth recovery

Most secrets are persisted in contextbay-data and survive container recreate. Here's how to recover from common scenarios.

Forgot the CB admin password

There's no built-in "forgot password" flow. Reset via the CLI inside the master container:

docker exec -it contextbay contextbay-cli user reset \
  --username admin --password '<new-password>'

Portainer admin password

Portainer's admin is bootstrapped by CB on first deploy (random password persisted in the CB DB). To rotate, delete the Portainer admin from the CB settings (PUT /api/settings/portainer_admin_password with empty value) and redeploy — the bootstrap will run again with a fresh password.

n8n owner credentials

Same model as Portainer — the n8n owner is auto-created on first boot. The CB-side encryption key is what unlocks n8n credentials, so don't rotate it lightly. Recovery is "reset n8n, re-import workflows":

docker volume rm cb-n8n_contextbay-n8n-data
docker exec contextbay contextbay-cli settings unset n8n_encryption_key
make deploy

JWT secret rotation

CB supports dual-secret rotation. Hit POST /api/admin/secrets/rotate-jwt (admin role). The previous secret is kept as [auth].jwt_secret_previous for the next session lifetime so existing tokens don't flip to invalid mid-flight.

Headscale recovery

If cb-headscale's data volume is corrupted, you have to re-enroll every worker. Master-side mesh identity persists in contextbay-data/tsnet; the worker side persists in each worker's /var/lib/contextbay/tsnet. Wipe either side's tsnet dir to force a re-handshake.

Log locations

Every CB service is a container — logs are docker logs first, then re-aggregated into cb-loki for retention.

SourceWhere
CB masterdocker logs contextbay
Workersdocker logs contextbay-worker on each host.
Sub-containersdocker logs cb-portainer, cb-headscale, cb-prometheus, etc.
Aggregatedcb-loki receives a tailed copy of every container's stdout. Query via /grafana → Explore → Loki, or use LogQL via the Logs page.
AuditEvery privileged action lands in the audit_log table — see GET /api/audit.

Common one-liners

Master health

curl -sS --max-time 5 http://localhost:7480/api/health

Container fleet across all endpoints

curl -sS http://localhost:7480/api/containers \
  -H "X-API-Key: cb_..." | jq '.[] | {name, state, endpoint}'

Prometheus targets

curl -sS http://localhost:7480/api/prometheus/targets \
  -H "X-API-Key: cb_..." | jq '.activeTargets[] | {scrapeUrl, health}'

Mesh nodes

curl -sS http://localhost:7480/api/mesh/nodes \
  -H "X-API-Key: cb_..." | jq '.[] | {name, ipv4, online}'

Recent enroll attempts for a host

curl -sS http://localhost:7480/api/hosts/<host_id>/enroll-attempts \
  -H "X-API-Key: cb_..." | jq '.[] | {ts, ip, reason}'

Trigger a backup job

curl -sS -X POST http://localhost:7480/api/backup-jobs/<job_id>/run \
  -H "X-API-Key: cb_..."

List sub-containers

curl -sS http://localhost:7480/api/admin/subcontainers \
  -H "X-API-Key: cb_..." | jq '.[] | {name, state, health}'

Related

  • Observability — Prometheus, Grafana, Loki, Tempo, host-onboarding counters.
  • Backups — what gets backed up, how to restore, verification.
  • Troubleshooting — error walkthroughs, debug commands.