Operations

Operations

Health checks, logs, migrations, backups, and recovery for self-hosted ctxpipe.

Operations docs assume the deployment is already configured. Use this page to keep a running instance healthy and to diagnose component-level failures.

Health checks

CheckCommand or routeExpected result
Backend statusGET /.statusJSON response with status: "ok" and timestamp
Postgres in ComposeDocker health check uses pg_isreadyService is healthy before migrations and app services start
UI through backendOpen AUTH_BASE_URLApp loads from the backend origin
MCP routenpx ctxpipe doctor mcp --url "$AUTH_BASE_URL/mcp" then an OAuth-capable MCP clientDoctor reports ready-for-oauth (or ready-for-api-key when CTXPIPE_API_KEY is set); client OAuth then succeeds
CodesearchAdd or reindex a repositoryRepository reaches indexed state; backend can call CODESEARCH_URL
Graph databaseOpen graph page or use graph-backed chat/MCPGraph responses succeed; backend does not return graph connectivity errors

Codesearch health

The reference codesearch service does not expose a separate public health endpoint. Treat successful repository clone/index/search operations as the practical health signal.

Migrations

The Compose deploy profile starts a migrate service before backend, worker, UI, and codesearch. Migrate uses the owner URL and provisions ctxpipe_app. App processes use the app-role DATABASE_URL. The migrate container runs:

bun run apps/backend/src/db/migrate.ts

The AWS CDK migrate task and the hosted deploy run the same script, migrate.ts. It does these steps in this order: it makes the ctxpipe_app role ready, applies the Drizzle and OpenWorkflow migrations, runs every data backfill, and then grants ctxpipe_app access to the tables.

After the Drizzle migrations, migrate runs the backfills. The org backfill fills a missing org_id on rows that the previous release wrote (the BEFORE INSERT triggers on repository_checkouts, confluence_spaces, and claim_evidence do the same at write time). You do not run it by hand. See Upgrades for the expand-only rules and the rollback runbook.

Migrations are idempotent. Drizzle tracks applied migrations in the drizzle.__drizzle_migrations table and skips migrations that already ran. A second run changes nothing.

Migrate reads the ctxpipe_app password from DATABASE_APP_PASSWORD:

  • When the password is set, migrate creates the role if it is missing and sets its password.
  • When the password is not set and the role exists, migrate only grants access.
  • When the password is not set and the role does not exist, migrate stops with exit code 1 before it changes the database. The error tells you how to fix it for each deployment path. Compose sends POSTGRES_PASSWORD, and the CDK migrate task sends the app database credentials secret.

For non-Compose deployments:

  1. Back up Postgres first.
  2. Run migrations once against the production database.
  3. Start the new backend, worker, UI, and codesearch versions after migrations complete.

Logs

ctxpipe writes structured application logs through evlog. Without an OTLP logs endpoint, logs go to stdout.

ComponentUseful signals
backendauth failures, REST errors, MCP requests, graph read failures, connector callbacks
workeringestion jobs, Confluence, Linear, PagerDuty, Buildkite, and Plain sync, Forge provisioning, background workflow failures
codesearchclone failures, indexing failures, Zoekt errors
postgresconnection limits, slow queries, disk pressure
graph databaseconnection failures, memory pressure, query errors

Configure OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, and OTEL_EXPORTER_OTLP_METRICS_ENDPOINT on the app services when you want telemetry to leave stdout and reach your observability backend. See Configuration for headers, service names, and Langfuse. ctxpipe does not run a collector for you. Export failures are visible in your collector or vendor, not in a bundled collector container.

Backups

Back up before upgrades, secret rotation, connector migrations, and storage changes.

DataBackup guidance
PostgresRequired. Use managed database backups or pg_dump/physical backups. Test restore regularly.
Graph databaseBack up or snapshot according to provider guidance. On Compose, mount a named volume first (see Docker).
repo_cacheOptional but recommended for large/private repositories. Rebuildable by recloning.
zoekt_index_deployOptional but recommended for large deployments. Rebuildable by reindexing.
SecretsExport from your secret manager or platform. Keep historical values needed for rollback.

Chat sandbox host (AWS)

@ctxpipe/aws-cdk runs Workspace chat sandboxes on one EC2 Docker host (see AWS: Chat sandbox host). It holds no data you need to back up: a replaced host starts empty and conversations continue from their git branch.

Find the instance from the SandboxHostAutoScalingGroupName stack output and open a shell with Session Manager (no SSH key or open port is needed):

aws ssm start-session --target <instance-id>
CheckCommand on the hostExpected result
Docker is upsudo systemctl is-active dockeractive
Docker volumedf -h /var/lib/dockerBelow 80% used. The disk alarm fires at 80%.
What uses the disksudo docker system dfImages, containers, and build cache sizes
Sandboxessudo docker ps -aOne container per recent conversation; stopped ones are idle sandboxes
Memory capsystemctl status ctxpipe-sandboxes.sliceMemory in use stays below the slice limit (85% of host memory)
Chat imagesudo docker image ls ghcr.io/ctxpipe-ai/chat-sandboxThe tag the backend's SANDBOX_CHAT_IMAGE names, after the first chat of a release
Workspace basessudo docker image ls ctxpipe-workspace-baseAt most about one image for each Workspace that had a new conversation in the last 30 days
Boot logsudo tail -n 100 /var/log/cloud-init-output.logEnds with the CloudFormation success signal

Sandboxes stop after 5 minutes idle and are removed 30 days after last use; an organization runs at most 50 at once. A Workspace base is an image of the Workspace repository after clone and setup, so that a new conversation starts without a clone. The periodic sandbox cleanup deletes a base when a newer base replaces it, and deletes the current base when no new conversation started from it for 30 days. The same cleanup removes objects with ctxpipe labels (ai.ctxpipe.*) that no database row records. If the volume still fills, check docker system df for what grows, then raise sandboxHost.dockerVolumeSizeGiB and run cdk deploy; the new size replaces the host.

Do not run docker system prune or docker container prune while conversations are active: they delete stopped sandboxes that conversations would resume. Do not run docker image prune -a: it deletes Workspace bases, and the next new conversation of each Workspace clones the repository again while a new base builds.

Chat sandboxes (Docker Compose)

The Compose deploy profile runs Workspace chat sandboxes inside the dind service (see Docker: Chat sandboxes). Its sandbox_docker volume holds no data you need to back up. Run these from the repository root:

CheckCommandExpected result
Daemon is updocker compose --profile deploy ps dindhealthy
Chat imagedocker compose --profile deploy logs chat-sandbox-imageEnds with the image ID (sha256:…)
Sandbox diskdocker compose exec dind df -h /var/lib/dockerRoom left on the host disk that holds the Docker volumes
What uses the diskdocker compose exec dind docker system dfImages, containers, and build cache sizes
Workspace basesdocker compose exec dind docker image ls ctxpipe-workspace-baseAt most about one image for each Workspace that had a new conversation in the last 30 days
Sandboxesdocker compose exec dind docker ps -aOne container per recent conversation; stopped ones are idle sandboxes
Memory capdocker compose exec dind cat /sys/fs/cgroup/ctxpipe-sandboxes/memory.current /sys/fs/cgroup/ctxpipe-sandboxes/memory.maxMemory in use stays below the cap. Missing on hosts without cgroup v2 (dind logs a warning).
Metadata blockdocker compose exec dind iptables -t raw -S PREROUTINGIncludes -d 169.254.169.254/32 -j DROP
No published data storesdocker port $(docker compose ps -q postgres falkordb)Prints nothing
No host socketdocker inspect --format '{{range .Mounts}}{{.Source}} {{end}}' $(docker compose ps -q backend worker)No /var/run/docker.sock

Sandboxes and Workspace bases are removed on the same schedule as on AWS. To free disk without touching sandboxes, remove untagged chat images and build cache:

docker compose exec dind docker image prune -f
docker compose exec dind docker builder prune -f

docker image prune without -a keeps every tagged image, so it keeps the Workspace bases. Do not run docker system prune or docker container prune inside dind while conversations are active: they delete stopped sandboxes that conversations would resume. Do not run docker image prune -a: it deletes Workspace bases, and the next new conversation of each Workspace clones the repository again while a new base builds.

Recovery runbooks

Workspace chat fails to start on Docker Compose

  • A 503 saying the Docker daemon is not reachable means dind is down: confirm it is healthy (docker compose --profile deploy ps -a) and check docker compose logs dind.
  • A 503 saying the chat sandbox image is missing means chat-sandbox-image has not finished or failed (for example a package mirror was unreachable, or the sandbox_docker volume was removed). Check its logs and rerun it: docker compose --profile deploy up -d chat-sandbox-image.
  • Check backend logs for Docker TLS errors. After the sandbox_ca volume is removed, restart backend and worker so they read the new certificates.
  • dind needs a privileged container. On a host that refuses one, chat is not available. Use the AWS CDK path, or run the sandboxes on another Docker host (see Chat sandboxes).

Workspace chat fails to start on AWS

  • Confirm the sandbox host instance is InService in its Auto Scaling group.
  • Check backend logs for Docker connection or TLS errors. Backend and worker reach the host at sandbox-host.ctxpipe.local:2376; the host registers that name when it finishes booting.
  • If the backend reports that the chat sandbox image is missing, the host could not pull ghcr.io/ctxpipe-ai/chat-sandbox. Check the host's outbound access to ghcr.io through NAT.
  • Terminate the instance to get a fresh host. The Auto Scaling group replaces it in a few minutes and reuses the stored TLS certificates.

App loads but sign-in fails

  • Confirm AUTH_BASE_URL matches the browser origin.
  • Confirm AUTH_SECRET is present and stable.
  • Confirm provider callback URLs use the public backend origin.
  • Check backend logs for Better Auth or OAuth provider errors.

Repository never becomes indexed

  • Check codesearch logs for clone/index errors.
  • Confirm GitHub App permissions and installation scope.
  • Confirm CODESEARCH_URL points at the codesearch service from the backend.
  • Reindex after fixing credentials or network access.

MCP discovery or OAuth fails

  • Confirm GET /.status returns { "status": "ok" }.
  • Run npx ctxpipe doctor mcp --url "$AUTH_BASE_URL/mcp".
  • A ready-for-oauth result means routing, the Bearer challenge, and OAuth discovery are coherent. It does not prove authenticated tools/list or a tool call.
  • Confirm AUTH_BASE_URL matches the public origin clients use, including TLS.
  • Confirm the MCP URL path is /mcp.
  • For authenticated OAuth, tools/list, and a list_workspaces call, use a version-pinned MCPJam client against a test organization. See MCP.

Chat or MCP returns weak context

  • Confirm repositories are indexed.
  • Confirm graph extraction has completed for the repository.
  • Check model provider credentials and rate limits.
  • Check backend and worker logs around code ingestion and graph projection.

Knowledge graph fails to load

  • Confirm GRAPH_DB_URI and provider credentials.
  • Confirm the graph database is reachable from backend and worker.
  • Check backend logs around graph snapshot/read operations.
  • Re-run repository ingestion if graph data needs to be rebuilt.

Confluence setup stalls

  • Confirm Atlassian OAuth callback URL.
  • Confirm Forge install URL or provisioning token.
  • Confirm the worker can run Forge provisioning and Confluence sync jobs.
  • See Confluence & Atlassian.

Linear setup stalls or content becomes stale

  • Confirm a Linear OAuth application is registered in the wizard or that LINEAR_* is present on backend and worker.
  • Confirm the Linear webhook secret is saved on the connection or LINEAR_WEBHOOK_SECRET is present on backend, and signed requests reach /api/v1/webhook/linear.
  • Confirm the selected GitHub App installation still has write access and that merge pushes reach /api/v1/webhook/github/<connectionId>.
  • Check worker logs for linear-sync-config, linear-sync-content, or linear-sync-incremental failures.
  • Choose Sync now in the menu of the connector card after you fix the underlying credential, network, or permission failure.
  • See Linear.

PagerDuty setup stalls or incidents become stale

  • Confirm PagerDuty OAuth credentials are present on backend and worker.
  • Confirm the connection is live and the incident's service is listed in the scope of a PagerDuty source with the status OK. There is no shared webhook secret; ctxpipe stores the per-subscription secret on the connection.
  • Confirm signed requests reach /api/v1/webhook/pagerduty and merge pushes reach /api/v1/webhook/github/<connectionId>.
  • Check worker logs for pagerduty-sync-config, pagerduty-sync-content, or pagerduty-sync-entity failures.
  • Choose Sync now in the menu of the connector card after you fix the underlying credential, network, or permission failure.
  • See PagerDuty.