Deployment Guide

Run Pathfinder anywhere — single container, full stack, or bare metal.

Docker (single container, bash-only)

The quickest Docker setup. No database needed — agents explore docs with shell commands only.

$ docker run -d \ -v ./pathfinder.yaml:/app/pathfinder.yaml:ro \ -v ./docs:/app/docs:ro \ -p 3001:3001 \ ghcr.io/copilotkit/pathfinder:latest

This mounts your config and local docs into the container. Agents get read-only access to your documentation via bash tools.

Docker Compose (full stack)

For semantic search, you need Postgres with pgvector. The production docker-compose.yml sets up everything:

# docker-compose.yml services: db: image: pgvector/pgvector:pg16 environment: POSTGRES_DB: pathfinder POSTGRES_USER: pathfinder POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U pathfinder"] interval: 5s timeout: 3s retries: 5 app: image: ghcr.io/copilotkit/pathfinder:latest ports: - "${PORT:-3001}:${PORT:-3001}" environment: DATABASE_URL: postgres://pathfinder:${POSTGRES_PASSWORD:-changeme}@db:5432/pathfinder OPENAI_API_KEY: ${OPENAI_API_KEY} GITHUB_TOKEN: ${GITHUB_TOKEN:-} GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:-} MCP_JWT_SECRET: ${MCP_JWT_SECRET} PATHFINDER_CONSENT_HMAC_KEY: ${PATHFINDER_CONSENT_HMAC_KEY} PATHFINDER_CONFIG: /app/pathfinder.yaml WORKSPACE_DIR: /data/workspaces PORT: ${PORT:-3001} NODE_ENV: production volumes: - ./pathfinder.yaml:/app/pathfinder.yaml:ro - workspaces:/data depends_on: db: condition: service_healthy volumes: pgdata: workspaces:

Create a .env file with your secrets, then start:

$ docker compose up -d

Persistent volume required for workspaces

Without a persistent volume mounted at /data, agent workspaces are lost on every container restart. If you use workspaces, always mount a volume. The workspaces volume in the compose file above handles this.

Railway

Deploy to Railway with a Postgres plugin and persistent volume:

  1. Create a new project on Railway and add a PostgreSQL database (use the pgvector template if available).
  2. Add a new service from the Pathfinder GitHub repo or Docker image (ghcr.io/copilotkit/pathfinder:latest).
  3. Set environment variables: DATABASE_URL (from Railway's Postgres), OPENAI_API_KEY, GITHUB_TOKEN (if needed).
  4. Add a persistent volume mounted at /data for workspace storage.
  5. Set WORKSPACE_DIR=/data/workspaces in the service environment.
  6. Deploy. The first boot auto-indexes your configured sources.

Generic VPS

Run Pathfinder directly on any Linux server with Node.js 20+.

systemd service

# /etc/systemd/system/pathfinder.service [Unit] Description=Pathfinder MCP Server After=network.target postgresql.service [Service] Type=simple User=pathfinder WorkingDirectory=/opt/pathfinder ExecStart=/usr/bin/node dist/server.js Environment=NODE_ENV=production Environment=PORT=3001 Environment=DATABASE_URL=postgresql://pathfinder:secret@localhost:5432/pathfinder Environment=OPENAI_API_KEY=sk-... Environment=MCP_JWT_SECRET=... Environment=PATHFINDER_CONSENT_HMAC_KEY=... Environment=PATHFINDER_CONFIG=/opt/pathfinder/pathfinder.yaml Environment=WORKSPACE_DIR=/var/lib/pathfinder/workspaces Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

nginx reverse proxy

# /etc/nginx/sites-available/pathfinder server { listen 443 ssl http2; server_name docs.example.com; ssl_certificate /etc/letsencrypt/live/docs.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/docs.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:3001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

Environment Variables

Variable Required Default Description
DATABASE_URL For search tools - PostgreSQL connection string (with pgvector)
MCP_JWT_SECRET Required in production - HMAC secret for signing OAuth access/refresh tokens. Generate with openssl rand -hex 32. Rotating this invalidates all issued tokens — clients will re-authenticate transparently. In any non-production environment (any NODE_ENV other than production) a random secret is generated per process and logged as a warning.
PATHFINDER_CONSENT_HMAC_KEY Required in production - HMAC secret for signing the consent-screen nonce on the OAuth /authorize/authorize/consent flow. Generate with openssl rand -hex 32. Comma-separated values are accepted for rotation — the first key signs, all keys verify; rotate by prepending a new key. Rotating invalidates only in-flight consent nonces (10-minute TTL) — issued access/refresh tokens are unaffected. In any non-production environment (any NODE_ENV other than production) a random ephemeral key is generated per process and logged as a warning.
OPENAI_API_KEY When embedding.provider is "openai" (default) - OpenAI API key for computing embeddings. Not needed for ollama or local providers.
GITHUB_TOKEN For private repos - GitHub PAT for cloning private repositories
GITHUB_WEBHOOK_SECRET For webhooks - Secret for validating GitHub webhook payloads
SLACK_BOT_TOKEN When slack sources configured - Slack bot OAuth token (xoxb-...)
SLACK_SIGNING_SECRET When using emoji-trigger - For Slack webhook signature verification
DISCORD_BOT_TOKEN When discord sources configured - Discord bot token
DISCORD_PUBLIC_KEY When discord sources configured - For Discord webhook Ed25519 verification
NOTION_TOKEN When notion sources configured - Notion internal integration token
PORT No 3001 HTTP port the server listens on
PATHFINDER_CONFIG No pathfinder.yaml Path to the config file
WORKSPACE_DIR No /tmp/pathfinder-workspaces Directory for agent workspace storage
NODE_ENV No development Set to production for deployed instances
LOG_LEVEL No info Logging verbosity (debug, info, warn, error)
CLONE_DIR No /tmp/mcp-repos Directory for git repo clones
ANALYTICS_TOKEN For privileged surfaces - Shared admin-access bearer token for all privileged surfaces — analytics (/api/analytics/*), Atlas ratification (/api/atlas/*), and admin ops (/admin/*). See Admin control surface.

MCP_JWT_SECRET must be set in production

MCP_JWT_SECRET MUST be set before NODE_ENV=production — the server will throw on startup if it's missing. See Authentication for how the OAuth flow uses this secret.

Optional dependencies

Some features require extra packages that are not bundled by default. Install only the ones your config needs:

Package When required Install
pdf-parse Any source has type: document with *.pdf file patterns npm install pdf-parse
mammoth Any source has type: document with *.docx file patterns npm install mammoth
@xenova/transformers embedding.provider is local npm install @xenova/transformers

Run pathfinder validate to detect missing optional dependencies and get install instructions.

Docker images: optional peer deps are not bundled

The default ghcr.io/copilotkit/pathfinder image (tags :latest / :<version>) is built with npm ci --omit=dev, which intentionally omits these optional peer dependencies to keep the image lean. For local embeddings (embedding.provider: local — transformers.js, the zero-API-key in-process provider), use the prebuilt ghcr.io/copilotkit/pathfinder:latest-local image (also tagged :<version>-local), which ships @xenova/transformers preinstalled. If you run the default image with embedding.provider: local and the peer is absent, the server now fails loudly at startup with an actionable message (rather than booting and throwing later at first embed). For type: document sources (PDF/DOCX), or to bake any optional peer into a derived image, uncomment the matching RUN npm install … line in the Dockerfile and rebuild (the -local variant is built from the same Dockerfile via --build-arg INCLUDE_LOCAL_EMBEDDINGS=true).

Health endpoint

Pathfinder exposes GET /health for monitoring. It returns JSON with uptime, indexing status, chunk counts per source, and index state (last indexed time, commit SHA, errors). Use it for load balancer health checks and deployment verification.

Webhook URLs

GitHub: Set your GitHub webhook's Payload URL to https://your-domain/webhooks/github.

Slack: Set your Slack app's Event Subscriptions Request URL to https://your-domain/webhooks/slack.

Discord: Set your Discord application's Interactions Endpoint URL to https://your-domain/webhooks/discord.

Admin control surface

Pathfinder exposes an authenticated control plane for operational tasks that would otherwise require database surgery and a redeploy — forcing a reindex, inspecting index state, and so on.

Authentication

All privileged surfaces — analytics (/api/analytics/*), Atlas ratification (/api/atlas/*), and admin ops (/admin/*) — share one admin-access bearer token: the ANALYTICS_TOKEN environment variable. Authenticate every request with an Authorization: Bearer $ANALYTICS_TOKEN header.

Force a reindex — POST /admin/reindex

Queues an indexing job and returns 202 Accepted. The body selects the scope:

An unknown source name or repo URL returns 400 Bad Request so a typo fails loud instead of silently no-op-ing.

$ curl -X POST https://your-domain/admin/reindex \ -H "Authorization: Bearer $ANALYTICS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"scope":"source","source":"my-docs"}'

Inspect index state — GET /admin/index-stats

Returns 200 OK with current index statistics (a POST /admin/index-stats alias is also accepted):

$ curl https://your-domain/admin/index-stats \ -H "Authorization: Bearer $ANALYTICS_TOKEN"

The response body has the shape:

{ "total_chunks": 1280, "by_source": { "my-docs": 1280 }, "indexed_repos": ["https://github.com/acme/docs"], "sources": [ /* per-source type, key, status, last_indexed, commit, error */ ] }

Volume Mounts

What you mount depends on your source configuration: