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
| Target | What it does |
|---|---|
make deploy | Build 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 redeploy | Alias 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
| Target | What it does |
|---|---|
make build | Build all three Go binaries (master + worker + cli) into dist/. |
make build-master | Master only. CGO_ENABLED=0 with version + commit + date ldflags. |
make build-worker | Worker only. |
make build-cli | CLI only. The contextbay-cli binary used by integration tests and scripted ops. |
make build-web | Build the Next.js frontend and emit static files for go-embed. |
make docker | Build local Docker images for the master + worker (does not push). |
Test + ready gate
| Target | What it does |
|---|---|
make test | Go tests with race detector across cmd/... and internal/.... |
make test-integration | Go integration suites under tests/integration/.... Hits a real Docker daemon; needs sockets. |
make test-web | Vitest run for the Next.js frontend. |
make test-agent | Jest run for the Claude Agent SDK service in agent-service/. |
make test-e2e | Playwright end-to-end tests against a running master. |
make ready | Pre-commit gate: fmt + lint + test + test-integration + test-web + test-agent + build + typecheck. If this passes, your branch is ready to push. |
Quality
| Target | What it does |
|---|---|
make fmt | gofmt across cmd + internal. |
make vet | go vet across master + worker code. |
make lint | golangci-lint if installed; otherwise no-op (CI is local now). |
make typecheck | TypeScript strict typecheck for the frontend (tsc --noEmit). |
make generate | Run all go generate (mockgen for store + backend). |
make proto | buf generate in proto/. |
make clean | Remove 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
| Volume | Holds | Depends on |
|---|---|---|
contextbay-data | CB SQLite DB, generated configs, JWT secret, n8n encryption key, knowledge vault, mesh state. | root of trust |
contextbay-portainer-data | Portainer BoltDB, admin password, JWT. | contextbay-data |
cb-n8n_contextbay-n8n-data | n8n 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 deployRemoving 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 deployJWT 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.
| Source | Where |
|---|---|
| CB master | docker logs contextbay |
| Workers | docker logs contextbay-worker on each host. |
| Sub-containers | docker logs cb-portainer, cb-headscale, cb-prometheus, etc. |
| Aggregated | cb-loki receives a tailed copy of every container's stdout. Query via /grafana → Explore → Loki, or use LogQL via the Logs page. |
| Audit | Every 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/healthContainer 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.

