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¶
- Go to https://railway.app/
- Click New Project → Deploy from GitHub repo
- Select
Constantinos-uni/crp-gateway(or your fork) - Railway auto-detects:
Dockerfile→ builds containerrailway.json→ sets healthchecks + 2 replicasrequirements.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 New → Database → Add 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 New → Database → Add 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¶
- Railway Dashboard → your Gateway service → Settings → Domains
- Click Generate Domain for a
*.up.railway.appURL - Or click Custom Domain and enter
gateway.crprotocol.io - Add the Railway-provided CNAME to your DNS provider
- Set
CRP_GATEWAY_URLandAPP_BASE_URLto match your custom domain
Step 4: Deploy¶
- Push any commit to the repo → Railway auto-deploys
- Or click Deploy in the Railway dashboard
- Watch the Deployments tab for build logs
- 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 - Healthcheck:
GET /healthshould return{"status":"ok"} - Readiness:
GET /readyshould 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.