Skip to content

Deploying Nexus

This guide covers deploying the Broker and Gateway to a production environment. For local development setup, see Deploy in Five Minutes.

Prerequisites

  • PostgreSQL 14 or later
  • Docker (for containerised deployment) or Go 1.22+ (for binary deployment)
  • TLS termination at your load balancer or reverse proxy

Generate secrets

Both services require secrets that must be generated before deployment and stored securely.

openssl rand -base64 32   # ENCRYPTION_KEY  — Broker only
openssl rand -base64 32   # STATE_KEY       — must be identical on Broker and Gateway
openssl rand -hex 32      # BROKER_API_KEY  — shared between Broker and Gateway

Store these in your secret manager (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, Kubernetes Secrets). Do not commit them to source control.

Broker environment variables

Variable Required Description
DATABASE_URL yes PostgreSQL connection string
ENCRYPTION_KEY yes 32-byte base64 key for AES-GCM token encryption
STATE_KEY yes 32-byte base64 key for OAuth state HMAC signing — must match Gateway
BASE_URL yes Public URL of the Broker, e.g. https://broker.internal.example.com
API_KEY conditional Single key the Gateway uses to authenticate with the Broker. Required unless API_KEYS, API_KEY_FILE, or API_KEYS_FILE supplies keys.
API_KEYS no Comma-separated Broker API keys for key rollover.
API_KEY_FILE no Mounted secret file containing one Broker API key; reloaded without process restart.
API_KEYS_FILE no Mounted secret file containing comma- or newline-separated Broker API keys; reloaded without process restart.
API_KEY_RELOAD_INTERVAL no File reload interval for API_KEY_FILE and API_KEYS_FILE (default: 30s).
REDIRECT_PATH no OAuth callback path (default: /auth/callback)
ALLOWED_CIDRS no Comma-separated CIDRs for IP allowlisting, e.g. 10.0.0.0/8
ALLOWED_RETURN_DOMAINS no Comma-separated allowed domains for return_url validation

Gateway environment variables

Variable Required Description
STATE_KEY yes Same value as the Broker's STATE_KEY
BROKER_BASE_URL yes Internal URL of the Broker, e.g. http://broker.internal:8080
BROKER_API_KEY yes The Broker's API_KEY
PORT no Port to listen on (default: 8090)
GATEWAY_REQUIRE_API_KEY yes, in production true: callers must present X-API-Key. The Gateway is the component agents reach, so this is the lock on token minting.
GATEWAY_API_KEYS with the above Keys for agents and SDKs (or GATEWAY_API_KEY_FILE for a mounted secret)

The Gateway's /health, /auth/callback and the credential-capture pages stay public; they are reached by load balancers and by the user's browser, not by the SDK.

Sidecar environment variables

The Sidecar is optional. Deploy it next to each agent that needs it (same pod or compose network), not as a shared central service, and never expose it publicly: it authenticates callers by network locality alone.

Variable Required Description
GATEWAY_BASE_URL yes URL of the Gateway, e.g. http://gateway:8090
GATEWAY_API_KEY when the Gateway requires a key One of the Gateway's GATEWAY_API_KEYS
NEXUS_ROUTES yes Allowlisted upstream routes, e.g. github=https://api.github.com
TOKEN_CACHE_TTL no Fallback credential cache lifetime for payloads without an expiry, e.g. 5m
REQUEST_BODY_LIMIT no Body buffer cap for signing strategies (default: 10MiB)
PORT no Port to listen on (default: 8070)

Network topology

Internet
    │
    ▼
Load Balancer / Reverse Proxy (TLS termination)
    │                   │
    ▼                   ▼
Gateway             Broker
(public or          (internal network only)
 internal)               │
    │                    ▼
    └───────────────► PostgreSQL

The Broker handles OAuth callbacks and must be reachable from the public internet at its BASE_URL/auth/callback. All other Broker traffic should be internal only.

The Gateway can be internal-only if your agents run inside the same network. Make it public only if agents run outside your network perimeter.

Database setup

Run the migration before starting the Broker:

cd nexus-broker
go run ./cmd/migrate

This creates the provider_profiles, connections, tokens, and audit_events tables.

Docker Compose (production-ready)

services:
  broker:
    image: ghcr.io/prescott-data/nexus-broker:latest
    environment:
      DATABASE_URL: postgres://nexus:${DB_PASSWORD}@postgres:5432/nexus
      ENCRYPTION_KEY: ${ENCRYPTION_KEY}
      STATE_KEY: ${STATE_KEY}
      BASE_URL: https://broker.internal.example.com
      API_KEY: ${BROKER_API_KEY}
      ALLOWED_CIDRS: "10.0.0.0/8"
    networks:
      - internal

  gateway:
    image: ghcr.io/prescott-data/nexus-gateway:latest
    environment:
      STATE_KEY: ${STATE_KEY}
      BROKER_BASE_URL: http://broker:8080
      BROKER_API_KEY: ${BROKER_API_KEY}
      PORT: "8090"
    ports:
      - "8090:8090"
    networks:
      - internal
      - public

  # Optional: co-located proxy for non-Go agents. Keep it on the internal
  # network only; it has no caller authentication of its own.
  sidecar:
    image: ghcr.io/prescott-data/nexus-sidecar:latest
    environment:
      GATEWAY_BASE_URL: http://gateway:8090
      NEXUS_ROUTES: "github=https://api.github.com"
      TOKEN_CACHE_TTL: 5m
    networks:
      - internal

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: nexus
      POSTGRES_USER: nexus
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - internal

networks:
  internal:
  public:

volumes:
  postgres_data:

Key management

ENCRYPTION_KEY must remain stable across deployments. Changing it makes every stored token permanently unreadable. Treat it as you would a database master password.

STATE_KEY can be rotated, but doing so invalidates all in-flight OAuth flows (pending connections at the moment of rotation). Any user mid-authorization will see an "invalid state" error and need to restart the flow.

Both keys must be identical across all instances of the same service. In Kubernetes, use a single Secret object mounted into both the Broker and Gateway pods for STATE_KEY.

For Broker API key rotation, prefer API_KEY_FILE or API_KEYS_FILE backed by a mounted secret. The Broker reloads those files on API_KEY_RELOAD_INTERVAL, so updating the secret volume can add or remove accepted keys without restarting the pod.

Provider client_secret values can be kept out of Postgres altogether with SECRET_BACKEND=gcp-secret-manager; see Provider Secret Backends.

Health checks

The Broker exposes GET /health and the Gateway exposes GET /health. Both return 200 OK when the service is ready. Configure your load balancer to use these endpoints. The Sidecar also exposes GET /health plus Prometheus metrics at GET /metrics.

Upgrading

Run database migrations before starting the new Broker version:

go run ./cmd/migrate

Migrations are additive and backward-compatible within the same minor version. Check the release notes for any breaking schema changes between major versions.