Configuration
The environment-variable reference for Cordy Gateway — database, Redis, provider keys, security, licensing, and runtime behavior.
Cordy Gateway is configured through environment variables. Start from deploy/env.template, which ships with placeholders and no real secrets. In the production Compose workflow, .env supplies interpolation values; the required override below passes settings that the base production file does not map into its containers.
Minimum required in production
These have no safe default and must be set for a production deployment:
DB_NAME,DB_USER,DB_PASSWORDDJANGO_SECRET_KEY,DJANGO_ALLOWED_HOSTS,DJANGO_ADMIN_URLCHANNEL_ENCRYPTION_KEYS(identical value for the Admin and Gateway services)
Generate secrets:
# DJANGO_SECRET_KEY
python -c "import secrets; print(secrets.token_urlsafe(50))"
# CHANNEL_ENCRYPTION_KEYS (a Fernet key)
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Database
| Variable | Default | Notes |
|---|---|---|
DB_NAME | — | Database name. Read directly by the Gateway data plane. If unset, the Gateway falls back to a dev database name and cannot reach your production DB. |
DB_USER | — | Database user. Read directly by the Gateway. |
DB_PASSWORD | — | Database password. Read directly by the Gateway. |
DB_HOST | postgres (Admin) / pgbouncer (Gateway) | The Gateway connects through PgBouncer; the Admin connects to Postgres directly. |
DB_PORT | 5432 | |
DATABASE_URL | — | Consumed by the Admin (and migration/test tooling) only. The Gateway data plane does not parse DATABASE_URL; it builds its DSN from DB_*. Set both consistently. |
Gotcha: the Gateway builds its Postgres DSN from
DB_NAME/DB_USER/DB_PASSWORD, not fromDATABASE_URL. If those are missing on thegatewayservice, it defaults to a development database name, cannot connect, and every request fails auth with401. Mirror the Admin'sDB_*values onto the Gateway.
Redis
| Variable | Default | Notes |
|---|---|---|
REDIS_URL | redis://localhost:6379/0 | Redis connection URL. In the prod compose this is redis://redis:6379/0. |
REDIS_POOL_SIZE | 20 | Connection pool size. 20 is the tuned default; the shipped template raises it to 500 for high concurrency. |
Provider keys
Provider and channel credentials are normally managed as encrypted Channels in the Admin, not in the environment. The following environment keys exist because the underlying LiteLLM integration reads them automatically; they are optional and mainly useful for development seeding. Prefer per-channel encrypted credentials for production.
| Variable | Provider |
|---|---|
OPENAI_API_KEY | OpenAI |
ANTHROPIC_API_KEY | Anthropic |
OPENROUTER_API_KEY | OpenRouter |
GEMINI_API_KEY | Google Gemini |
AZURE_API_KEY, AZURE_API_BASE, AZURE_API_VERSION | Azure OpenAI |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION_NAME | AWS Bedrock |
COHERE_API_KEY | Cohere |
GROQ_API_KEY | Groq |
TOGETHERAI_API_KEY | Together AI |
DEEPINFRA_API_KEY | DeepInfra |
REPLICATE_API_TOKEN | Replicate |
HUGGINGFACE_API_KEY | Hugging Face |
See Providers for how channels and credentials work.
Security
| Variable | Default | Notes |
|---|---|---|
CHANNEL_ENCRYPTION_KEYS | — | Comma-separated Fernet keys that encrypt channel upstream credentials at rest. The first is the active (encrypting) key; the rest allow rotation. Mandatory in production — the Admin and Gateway refuse to start without it, and both must use the same value. |
CLIENT_CORS_ORIGINS | empty | Comma-separated Origins allowed to call the Gateway from a browser. Empty denies all cross-origin browser access (server-to-server callers do not use CORS). A literal * is refused at boot in production. |
CLIENT_CORS_ALLOW_WILDCARD_IN_PRODUCTION | false | Unsafe escape hatch to permit a * client CORS origin in production. Leave off. |
TRUSTED_PROXY_CIDRS | empty | CIDRs of trusted reverse proxies. X-Forwarded-For / X-Real-IP are honoured for the logged client IP only when the socket peer matches one of these; otherwise the socket peer is used. Prevents audit-IP spoofing. |
GATEWAY_METRICS_TOKEN | empty | Bearer token scrapers must present for /metrics/prometheus. Use this for a production endpoint reached through public nginx. With neither this nor an allowed CIDR set, production returns 403. |
GATEWAY_METRICS_ALLOWED_CIDRS | empty | CIDRs allowed to scrape without a token. The check uses the immediate socket peer and runs before token validation. Behind public nginx, allowing nginx or its network would also admit public requests proxied through it; leave this empty. Use CIDRs only for a direct, isolated trusted scrape path. |
DJANGO_SECRET_KEY | — | Django cryptographic key (Admin). Required in production. |
DJANGO_ALLOWED_HOSTS | — | Comma-separated hostnames the Admin serves. Required in production. |
DJANGO_ADMIN_URL | — | Admin URL path prefix; must end with /. Pick a non-obvious value. Required in production. |
DJANGO_CSRF_TRUSTED_ORIGINS | empty | Scheme-qualified origins allowed to POST to the Admin behind a TLS proxy (e.g. https://admin.example.com). |
DJANGO_SECURE_SSL_REDIRECT | True | Force HTTP→HTTPS redirect. Set False only for a local no-TLS test. |
ADMIN_HTTP_PORT | 8001 | Host port the Admin is published on. |
GATEWAY_HTTP_PORT | 8002 | Host port the Gateway (via nginx) is published on. |
Admin and member portal
| Variable | Default | Notes |
|---|---|---|
CORDY_GATEWAY_BASE_URL | http://127.0.0.1:8002/v1 | Base URL shown in the /my/ member quickstart. Set it to the client-reachable HTTPS URL in production; it does not change Gateway binding. |
ADMIN_LANGFUSE_TRACE_URL_TEMPLATE | empty | Optional external trace link shown in Request logs and member usage. Must be an HTTP(S) URL containing the literal {trace_id}, for example https://langfuse.example.com/trace/{trace_id}. |
Both are Admin-process variables and must be passed through by the deployment override below.
Required production Compose override
docker-compose.prod.yml does not load .env as a container env_file. The --env-file .env option only supplies ${...} interpolation, and the base file does not map ENVIRONMENT or the security and runtime variables below into gateway. With the base file alone, the Gateway therefore uses its development default: an empty CORS list falls back to *, metrics is open when neither metrics control is set, .env tuning values that are not referenced are ignored, and the credit-consume WAL remains disabled.
Create docker-compose.production-overrides.yml beside the base file. This corrects the effective deployment; it does not change the upstream docker-compose.prod.yml:
services:
admin:
environment:
CORDY_GATEWAY_BASE_URL: ${CORDY_GATEWAY_BASE_URL:?set CORDY_GATEWAY_BASE_URL}
ADMIN_LANGFUSE_TRACE_URL_TEMPLATE: ${ADMIN_LANGFUSE_TRACE_URL_TEMPLATE:-}
gateway:
environment:
ENVIRONMENT: production
CLIENT_CORS_ORIGINS: ${CLIENT_CORS_ORIGINS:-}
CLIENT_CORS_ALLOW_WILDCARD_IN_PRODUCTION: ${CLIENT_CORS_ALLOW_WILDCARD_IN_PRODUCTION:-false}
TRUSTED_PROXY_CIDRS: ${TRUSTED_PROXY_CIDRS:-}
GATEWAY_METRICS_TOKEN: ${GATEWAY_METRICS_TOKEN:-}
GATEWAY_METRICS_ALLOWED_CIDRS: ${GATEWAY_METRICS_ALLOWED_CIDRS:-}
GATEWAY_DEFAULT_ROUTING_STRATEGY: ${GATEWAY_DEFAULT_ROUTING_STRATEGY:-weighted}
GATEWAY_MAX_BODY_BYTES: ${GATEWAY_MAX_BODY_BYTES:-10485760}
GATEWAY_MAX_INFLIGHT_PER_KEY: ${GATEWAY_MAX_INFLIGHT_PER_KEY:-50}
GATEWAY_DEFAULT_MAX_COMPLETION_TOKENS: ${GATEWAY_DEFAULT_MAX_COMPLETION_TOKENS:-4096}
GATEWAY_PRICE_TTL_SECONDS: ${GATEWAY_PRICE_TTL_SECONDS:-300}
IDEMPOTENCY_TTL_SECONDS: ${IDEMPOTENCY_TTL_SECONDS:-600}
GATEWAY_CREDIT_CONSUME_WAL: /var/lib/cordy/credit-consume.jsonl
OTEL_TRACING_ENABLED: ${OTEL_TRACING_ENABLED:-false}
OTEL_SERVICE_NAME: ${OTEL_SERVICE_NAME:-cordy-gateway}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
OTEL_EXPORTER_OTLP_HEADERS: ${OTEL_EXPORTER_OTLP_HEADERS:-}
OTEL_SHUTDOWN_TIMEOUT_SECONDS: ${OTEL_SHUTDOWN_TIMEOUT_SECONDS:-2.0}
volumes:
- gateway_wal:/var/lib/cordy
volumes:
gateway_wal:
Set CORDY_GATEWAY_BASE_URL=https://gateway.example.com/v1 and, when used, ADMIN_LANGFUSE_TRACE_URL_TEMPLATE=https://langfuse.example.com/trace/{trace_id} in .env. Set explicit browser origins and trusted proxy CIDRs when your topology needs them. For a public nginx deployment, set a strong GATEWAY_METRICS_TOKEN and leave GATEWAY_METRICS_ALLOWED_CIDRS empty; the CIDR check sees nginx as the peer, not the original public client.
Apply both files whenever starting, recreating, or scaling the stack:
docker compose \
-f docker-compose.prod.yml \
-f docker-compose.production-overrides.yml \
--env-file .env up -d
The named volume makes /var/lib/cordy/credit-consume.jsonl durable across container replacement. The shipped Gateway image declares no USER, so it runs as root and can write the mounted directory. If you redistribute the image under a custom UID, create and grant that UID write access to /var/lib/cordy; the application opens the file directly and does not create a missing parent directory.
When the Redis consume-buffer write fails, the Gateway appends a JSON-lines cost record to this WAL and lets the request finish. If the path is empty or the append also fails, it records a consume_drop_total under-charge instead. The repository has no automated WAL replay command. Preserve the file as billing evidence and reconcile it with request logs, the credit ledger, and upstream invoices before applying any manual adjustment; do not feed the file to an undocumented command.
Licensing
Cordy Gateway uses an offline, HMAC-signed license. Licensing defaults to audit-only (fail-open); it does not gate a normal deployment.
| Variable | Default | Notes |
|---|---|---|
LICENSE_KEY | empty | HMAC-signed license payload. Blank for unlicensed/dev. |
LICENSE_SIGNING_SECRET | empty | Shared secret used to verify the license, provided per customer. |
LICENSE_CUSTOMER_ID | empty | Expected customer id to match the payload; blank to skip. |
LICENSE_NODE_ID | node-1 | Current node id for node-count checks. |
LICENSE_ENFORCEMENT_MODE | audit_only | audit_only (fail-open, logs only) or strict (fail-closed). |
Runtime behavior
| Variable | Default | Notes |
|---|---|---|
GATEWAY_DEFAULT_ROUTING_STRATEGY | weighted | Default channel-selection strategy: weighted, cheapest, fastest, or region_aware. Overridable per request with X-Routing-Strategy. |
GATEWAY_MAX_BODY_BYTES | 10485760 (10 MiB) | Hard request-body cap. Larger bodies get 413. <= 0 disables the cap. |
GATEWAY_MAX_INFLIGHT_PER_KEY | 50 | Max concurrent in-flight requests per API key. <= 0 disables. |
GATEWAY_DEFAULT_MAX_COMPLETION_TOKENS | 4096 | Completion-token estimate used for quota admission when a request sets no max_tokens. |
GATEWAY_PRICE_TTL_SECONDS | 300 | Per-process TTL for the effective-price cache used in cost snapshots. |
GATEWAY_CREDIT_CONSUME_WAL | empty | Append-only JSON-lines fallback for credit-consume costs when the Redis buffer write fails. The production override sets a durable path; an empty or unwritable path can leave a counted under-charge. |
GRANIAN_WORKERS | 8 | Granian worker processes per Gateway container. |
IDEMPOTENCY_TTL_SECONDS | 600 | Lifetime of a stored idempotent response / lock. |
Observability (OpenTelemetry)
Tracing is off by default. The production override maps every supported OTEL_* field into gateway. Configure an operator-managed OTLP/HTTP collector that is reachable from the Gateway container:
OTEL_TRACING_ENABLED=true
OTEL_SERVICE_NAME=cordy-gateway
OTEL_EXPORTER_OTLP_ENDPOINT=https://telemetry.example.com/v1/traces
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <collector-token>"
OTEL_SHUTDOWN_TIMEOUT_SECONDS=2.0
Apply the updated environment to the Gateway:
docker compose \
-f docker-compose.prod.yml \
-f docker-compose.production-overrides.yml \
--env-file .env up -d gateway
The supplied docker-compose.tracing.yml and docker-compose.observability.yml are development/reference profiles. They attach collectors on networks that the production Gateway does not join, and the observability profile ships CHANGEME defaults. Do not combine them with the production stack without separately engineering and validating networking, secrets, storage, and access controls.
| Variable | Default | Notes |
|---|---|---|
OTEL_TRACING_ENABLED | false | Enable OpenTelemetry tracing for the Gateway. |
OTEL_SERVICE_NAME | cordy-gateway | service.name reported in traces. |
OTEL_EXPORTER_OTLP_ENDPOINT | empty | OTLP HTTP endpoint for trace export. |
OTEL_EXPORTER_OTLP_HEADERS | empty | Comma-separated OTLP exporter headers. |
OTEL_SHUTDOWN_TIMEOUT_SECONDS | 2.0 | Bounded flush timeout on shutdown. |
Scaling and infrastructure
These tune the compose stack and horizontal scaling. See Deployment.
| Variable | Default | Notes |
|---|---|---|
SCALE_GRANIAN_WORKERS | 4 | Per-replica worker count when running multiple gateway replicas. |
ADMIN_WORKERS | 3 | Gunicorn workers for the Admin service. |
PGBOUNCER_POOL_SIZE | 100 | PgBouncer default pool size. |
PGBOUNCER_MAX_CLIENT_CONN | 400 | PgBouncer max client connections. |
REDIS_MAXMEMORY | 512mb | Redis max memory (with volatile-lru eviction that never touches money keys). |
Backups
| Variable | Default | Notes |
|---|---|---|
BACKUP_DIR | ./backups in scripts | Where logical backups are written. The production template recommends /var/backups/cordy. |
BACKUP_RETENTION | 7 | Backups to retain. |
REQUIRE_PITR | 0 | Set to 1 once WAL archiving / point-in-time recovery is enabled to enforce it in acceptance checks. |
Next steps
- Deployment — apply these settings to a production topology.
- Operations — verify health, backups, upgrades, and traces.
- Security — the security-relevant variables in context.