This guide covers deploying Rook as a production AI gateway, either locally with Docker or in a cloud environment.
- Docker (for containerized deployment)
- Ollama Cloud API key (for cloud providers)
Rook requires several environment variables depending on the features you enable:
# Encryption passphrase (min 12 characters, keep secret!)
ENCRYPTION_PASSPHRASE="your-secure-passphrase-here"
# Encryption salt (generate once with: openssl rand -base64 16 | tr -d '=' | tr '+/' '-_')
ENCRYPTION_SALT="Z3G83UBdTUkfGGWr-QDnQg"# Secret for hashing client API keys (generate with: openssl rand -hex 32)
API_KEY_HASH_SECRET="your-hash-secret-here"
⚠️ CRITICAL: This secret MUST remain stable. If it changes, ALL existing API keys become invalid. See API Key Troubleshooting for recovery steps if this happens.
# Log level (default: info)
RUST_LOG=info
# Config file path (default: ~/.config/cortex/rook.toml)
ROOK_CONFIG=/app/config/rook.tomlFor a dashboard at https://rook.example.com:8443, pass this environment variable to Rook:
ALLOWED_ORIGINS=https://rook.example.com:8443For Docker use -e ALLOWED_ORIGINS="https://rook.example.com:8443"; for Compose add it under environment. Use the public dashboard origin, including its scheme and port, when deploying behind a reverse proxy. Register https://rook.example.com:8443/api/oauth/callback with the OAuth provider.
ALLOWED_ORIGINS replaces the three default CORS origins: http://localhost:3000, http://localhost:4747, and http://127.0.0.1:3000. Browser origins match entries exactly; different ports and subdomains need separate entries. Wildcard (*) entries are unsupported and fail closed, disabling all cross-origin browser access with a warning. Use explicit origins only; wildcard CORS is unsafe with cookie-authenticated management routes. Non-loopback OAuth callbacks also require an explicit HTTPS origin, matched by scheme, host, and effective port.
Loopback OAuth callbacks (localhost, 127.0.0.1, [::1]) may use HTTP or HTTPS on any port without an entry. Every callback must use /api/oauth/callback with no credentials, query, or fragment. This exception does not expand CORS permissions. See configuration for the full contract.
The release Dockerfile consumes a prepared binary context; it is not a
source-build Dockerfile. Build a local Rook binary first and place it at the
architecture-specific context path:
# Build on a Linux/amd64 host, or cross-compile this target with the required
# Linux linker/toolchain installed. This Dockerfile consumes an amd64 Linux binary.
cargo build --locked --release --target x86_64-unknown-linux-gnu -p rook
mkdir -p docker-context
cp target/x86_64-unknown-linux-gnu/release/rook docker-context/rook-amd64
cp apps/rook/Dockerfile docker-context/Dockerfile
docker build --platform linux/amd64 -f docker-context/Dockerfile -t rook:local docker-context
# Multi-architecture publishing is enabled only when the corresponding target
# has product-specific build and smoke-test evidence. See docs/releasing.md.# Create config directory
mkdir -p rook-config
# Create config file (rook-config/rook.toml)
cat > rook-config/rook.toml << 'EOF'
[server]
host = "0.0.0.0"
port = 3773
[routing]
strategy = "priority"
[cache]
enabled = true
ttl_secs = 300
[database]
db_path = "/app/data/rook.db"
[auth.api_keys]
enabled = false
allow_env_fallback = true
[provider_crud]
enabled = true
EOF
# Run the container
docker run -d \
--name rook \
-p 3773:3773 \
-v $(pwd)/rook-config:/app/config:ro \
-v rook-data:/app/data \
-e ENCRYPTION_PASSPHRASE="your-secure-passphrase-min-12-chars" \
-e ENCRYPTION_SALT="your-generated-salt" \
-e RUST_LOG=info \
rook:latest
# Verify it's running
curl http://localhost:3773/healthversion: '3.8'
services:
rook:
image: rook:latest
ports:
- "3773:3773"
volumes:
- ./rook-config/rook.toml:/app/config/rook.toml:ro
- rook-data:/app/data
environment:
- ENCRYPTION_PASSPHRASE=${ENCRYPTION_PASSPHRASE}
- ENCRYPTION_SALT=${ENCRYPTION_SALT}
- RUST_LOG=info
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:3773/health" ]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
volumes:
rook-data:Create a .env file (never commit this to version control):
# Required for provider_crud
ENCRYPTION_PASSPHRASE=your-secure-passphrase-min-12-chars
ENCRYPTION_SALT=Z3G83UBdTUkfGGWr-QDnQg
# Required for auth.api_keys (optional)
API_KEY_HASH_SECRET=your-hash-secret-hereAfter Rook is running, add providers via the REST API:
# Add Ollama Cloud provider
curl -X POST http://localhost:3773/api/providers \
-H "Content-Type: application/json" \
-d '{
"name": "ollama-cloud-primary",
"provider_kind": "ollama-cloud",
"auth_type": "api_key",
"credentials": {
"api_key": "ollama-xxxxxxxxxxxxxxxx"
},
"is_active": true,
"priority": 1
}'
# Verify provider was added
curl http://localhost:3773/api/providers- Navigate to
http://localhost:3773/providers - Click "Add Provider"
- Select "Ollama Cloud"
- Enter your API key from ollama.com/settings/keys
- Click "Test Connection" to verify
- Click "Save"
# List available models
curl http://localhost:3773/v1/models \
-H "Authorization: Bearer your-client-api-key"
# Non-streaming request
curl -X POST http://localhost:3773/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-client-api-key" \
-d '{
"model": "llama3.2",
"messages": [{"role": "user", "content": "Hello!"}]
}'
# Streaming request
curl -X POST http://localhost:3773/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-client-api-key" \
-d '{
"model": "llama3.2",
"messages": [{"role": "user", "content": "Count to 5"}],
"stream": true
}'# Overall health
curl http://localhost:3773/health
# Per-provider health (after providers are added)
curl http://localhost:3773/api/telemetry/summary- Generate and securely store
ENCRYPTION_PASSPHRASEandENCRYPTION_SALT - Use a persistent database path (not in-memory)
- Enable
auth.api_keysfor production if exposing to network - Configure rate-limiting if needed
- Set up monitoring/log aggregation
- Use a reverse proxy (nginx, Caddy, etc.) for TLS termination
- Consider adding health checks and auto-restart policies
apiVersion: apps/v1
kind: Deployment
metadata:
name: rook
spec:
replicas: 2
selector:
matchLabels:
app: rook
template:
metadata:
labels:
app: rook
spec:
containers:
- name: rook
image: rook:latest
ports:
- containerPort: 3773
env:
- name: ENCRYPTION_PASSPHRASE
valueFrom:
secretKeyRef:
name: rook-secrets
key: passphrase
- name: ENCRYPTION_SALT
valueFrom:
secretKeyRef:
name: rook-secrets
key: salt
- name: RUST_LOG
value: "info"
volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
- name: data
mountPath: /app/data
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 3773
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 3773
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: config
configMap:
name: rook-config
- name: data
persistentVolumeClaim:
claimName: rook-data
---
apiVersion: v1
kind: Service
metadata:
name: rook
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 3773
selector:
app: rook# Generate encryption salt
openssl rand -base64 16 | tr -d '=' | tr '+/' '-_'
# Example output: Z3G83UBdTUkfGGWr-QDnQg
# Generate API key hash secret
openssl rand -hex 32
# Example output: a1b2c3d4e5f6...
# Generate a client API key (for auth.api_keys)
openssl rand -hex 32
# Example output: rk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx