Skip to content

CRP Gateway — Railway Deployment Guide

Overview

CRP Gateway is a FastAPI wrapper around crp.gateway.GatewayRequestLifecycle. It exposes OpenAI-compatible endpoints with full CRP governance.

Critical architectural principle: The gateway does not store LLM provider keys in its own environment. Each user (tenant) supplies their own OpenAI, Anthropic, or local-LLM credentials through the gateway's secure key-vault API. The gateway only stores its own operational secrets.

Repo: Constantinos-uni/crp-gateway
Runtime: Python 3.13 + FastAPI + Uvicorn
Port: 8100 (default)
Database: PostgreSQL (required for production)
Cache/State: Redis (required for multi-replica)


Step 1: Connect Repo to Railway

  1. Go to https://railway.app/
  2. Click New ProjectDeploy from GitHub repo
  3. Select Constantinos-uni/crp-gateway (or your fork)
  4. Railway auto-detects:
  5. Dockerfile → builds container
  6. railway.json → sets healthchecks + 2 replicas
  7. requirements.txt → installs deps

Step 2: Add Environment Variables

In Railway Dashboard → your Gateway service → Variables, add these EXACT variables.

DO NOT put user LLM keys here

OPENAI_API_KEY, ANTHROPIC_API_KEY, and local-LLM endpoints are user-provided per tenant. They are captured through the gateway UI/API and stored encrypted. Never set them as Railway-wide environment variables.

Copy-paste block

# --- Core gateway secrets (set these manually) ---
SECRET_KEY=replace-with-32-char-random-string
CRP_GATEWAY_URL=https://gateway.crprotocol.io
APP_BASE_URL=https://gateway.crprotocol.io
ALLOWED_ORIGINS=https://crprotocol.io,https://comply.crprotocol.io
LOG_LEVEL=info

# --- PostgreSQL (add Railway Postgres service first, then reference it) ---
DATABASE_URL=${{Postgres.DATABASE_URL}}

# --- Redis (add Railway Redis service first, then reference it) ---
REDIS_URL=${{Redis.REDIS_URL}}

# --- Clerk authentication (shared across all CRP products) ---
# Issuer/JWKS root for verifying Clerk JWTs
CLERK_ISSUER=https://clerk.crprotocol.io
CLERK_SECRET_KEY=sk_live_...
CLERK_PUBLISHABLE_KEY=pk_live_...

# --- Stripe billing (get from Stripe Dashboard) ---
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Monthly price IDs
STRIPE_GATEWAY_DEVELOPER_PRICE_ID=price_...
STRIPE_GATEWAY_TEAM_PRICE_ID=price_...

# Annual price IDs
STRIPE_GATEWAY_DEVELOPER_ANNUAL_PRICE_ID=price_...
STRIPE_GATEWAY_TEAM_ANNUAL_PRICE_ID=price_...

# --- Comply webhook for audit streaming ---
CRP_COMPLY_WEBHOOK_URL=https://comply.crprotocol.io/api/v1/webhook/gateway-audit

How to add each service

PostgreSQL: 1. In your Railway project, click NewDatabaseAdd PostgreSQL 2. Railway creates it and auto-injects DATABASE_URL as a reference variable 3. In your Gateway service Variables, click New Variable → type DATABASE_URL → select ${{Postgres.DATABASE_URL}} from the dropdown

Redis: 1. Click NewDatabaseAdd Redis 2. Railway creates it and auto-injects REDIS_URL 3. In your Gateway service Variables, click New Variable → type REDIS_URL → select ${{Redis.REDIS_URL}} from the dropdown 4. CRITICAL: Redis is required when numReplicas > 1 in railway.json


Step 3: Configure Custom Domain

  1. Railway Dashboard → your Gateway service → SettingsDomains
  2. Click Generate Domain for a *.up.railway.app URL
  3. Or click Custom Domain and enter gateway.crprotocol.io
  4. Add the Railway-provided CNAME to your DNS provider
  5. Set CRP_GATEWAY_URL and APP_BASE_URL to match your custom domain

Step 4: Deploy

  1. Push any commit to the repo → Railway auto-deploys
  2. Or click Deploy in the Railway dashboard
  3. Watch the Deployments tab for build logs
  4. If you see cached layers and old code, add a trivial change (e.g., edit a comment in Dockerfile) and push again to bust the cache
  5. Healthcheck: GET /health should return {"status":"ok"}
  6. Readiness: GET /ready should return {"status":"ready"}

Step 5: How Users Add Their LLM Keys

After deployment, users never touch Railway env vars. They add keys through the gateway:

Via the REST API

# 1. Create a tenant
curl -X POST https://gateway.crprotocol.io/v1/tenants \
  -H "Content-Type: application/json" \
  -d '{"name":"my-team","email":"user@example.com"}'
# Response: {"tenant_id":"tnt_...","api_key":"crp_gw_...","plan":"free"}

# 2. Register their own OpenAI key
curl -X POST https://gateway.crprotocol.io/v1/providers/openai \
  -H "Authorization: Bearer crp_gw_..." \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "sk-user-own-key",
    "default_model": "gpt-4o"
  }'

# 3. Test chat completion
curl -X POST https://gateway.crprotocol.io/v1/chat/completions \
  -H "Authorization: Bearer crp_gw_..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role":"user","content":"Hello"}]
  }'

Local / Custom LLM

Users explicitly set their own endpoint:

curl -X POST https://gateway.crprotocol.io/v1/providers/custom \
  -H "Authorization: Bearer crp_gw_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "local-ollama",
    "base_url": "http://localhost:11434/v1",
    "api_key": "unused",
    "default_model": "llama3.1"
  }'

Why this architecture?

  • You (the operator) do not hold user API keys
  • Users can rotate keys without redeploying the gateway
  • Compromise of Railway env vars does not leak provider keys
  • Multi-tenant by design -- each tenant has isolated key storage

Multi-Replica Warning

railway.json sets numReplicas: 2 by default.

Without Redis With Redis
Sessions lost on replica restart Sessions persisted
Rate limits per-replica only Global rate limits
Webhook dedupe fails Shared dedupe state
Billing counts diverge Accurate billing

Always add Redis before scaling to 2+ replicas.


Troubleshooting

Issue Fix
{"detail":"Not Found"} on / Check custom domain DNS. Try https://your-service.up.railway.app directly. If still 404, Railway may be serving a cached build -- push a trivial change to bust cache.
hnswlib build fails Dockerfile needs build-essential gcc g++ python3-dev. Already in latest Dockerfile.
Health check fails Ensure port matches railway.json (PORT: 8100)
Database connection error Verify DATABASE_URL is injected; add PostgreSQL service
502 Bad Gateway Check numReplicas -- if >1, add Redis
Stripe webhook fails Verify STRIPE_WEBHOOK_SECRET matches Stripe Dashboard
CORS errors Add your frontend domain to ALLOWED_ORIGINS
User says "no provider configured" They must add their own key via /v1/providers -- this is by design

Files in crp-gateway repo

crp-gateway/
  main.py              # FastAPI app (reads DATABASE_URL, REDIS_URL)
  crp_gateway/         # Re-exports from crp.gateway
  requirements.txt     # crprotocol[full]>=4.0.0, fastapi, uvicorn
  Dockerfile           # Python 3.13-slim + build tools
  railway.json         # 2 replicas, healthchecks, rolling deploy
  docker-compose.yml   # Local dev stack
  .env.example         # Operational env vars only (no user LLM keys)
  README.md            # Quick start

Last updated: 2026-06-07. Keep in sync with crp-gateway/railway.json.