The auth service runs as a separate container alongside the API service.
Architecture
┌─────────────┐
│ Railway │
│ (or local │
│ Docker) │
└──────┬──────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌───────▼──────┐
│ API :8000 │ │ Auth :8001 │ │ Orch :8002 │
│ (Dockerfile │ │ (Dockerfile │ │ │
│ .api) │ │ .auth) │ │ │
└──────┬──────┘ └──────┬──────┘ └──────────────┘
│ │
┌────┴────────────────┴────┐
│ │
┌────▼────┐ ┌──────▼──────┐
│Postgres │ │ Redis │
│ (auth │ │ (bloom │
│ schema) │ │ filter) │
└─────────┘ └─────────────┘
Docker
Dockerfile
docker/Dockerfile.auth:
- Base:
python:3.11-slim
- Runs
python scripts/migrate.py on startup (includes 004_auth_schema.sql)
- Starts uvicorn on port 8001 with 2 workers
- Same build pattern as
Dockerfile.api
Docker Compose
The auth service is defined in docker-compose.yml:
auth:
build:
context: .
dockerfile: docker/Dockerfile.auth
ports:
- "8001:8001"
environment:
AUTH_DATABASE_URL: postgresql+asyncpg://quint:quint_local_dev@postgres:5432/quint
REDIS_URL: redis://redis:6379/0
AUTH_PORT: "8001"
AUTH_MASTER_KEY: "local-dev-master-key-do-not-use-in-prod"
depends_on:
postgres: { condition: service_healthy }
redis: { condition: service_healthy }
volumes:
- ./src:/app/src
The API service connects to the auth service via:
AUTH_SERVICE_URL: http://auth:8001
Starting locally
# Start everything
docker compose up -d
# Start just auth + dependencies
docker compose up -d postgres redis auth
# View auth service logs
docker compose logs -f auth
Environment Variables
Auth Service (src/auth_service/config.py)
| Variable | Default | Description |
|---|
AUTH_DATABASE_URL | (required) | Postgres connection string |
REDIS_URL | redis://localhost:6379/0 | Redis connection |
AUTH_PORT | 8001 | Service port |
AUTH_MASTER_KEY | (required) | AES-256-GCM encryption key for private keys |
AUTH_APP_TOKEN_TTL_DAYS | 365 | Default app token TTL |
AUTH_BEARER_TOKEN_TTL_DAYS | 90 | Default bearer token TTL |
AUTH_AGENT_TOKEN_TTL_HOURS | 24 | Default agent token TTL |
AUTH_SUBAGENT_TOKEN_TTL_HOURS | 4 | Default subagent token TTL |
AUTH_SESSION_TOKEN_TTL_MINUTES | 60 | Default session token TTL |
AUTH_OVERRIDE_TOKEN_TTL_MINUTES | 5 | Default override token TTL |
API Service (additional)
| Variable | Default | Description |
|---|
AUTH_SERVICE_URL | http://localhost:8001 | Auth service base URL |
Database Migration
Migration 004_auth_schema.sql creates the auth schema. It is idempotent — safe to run multiple times.
# Run manually
python scripts/migrate.py
# Or it runs automatically on container startup
Railway Deployment
CI/CD Pipeline (.github/workflows/deploy.yml)
test → deploy-api ─────────────┐
→ deploy-auth ─────────────┤→ smoke-test
→ deploy-orchestrator ─────┘
The deploy-auth job:
- Uses Railway CLI (
railwayapp/cli-action@v2)
- Deploys from
docker/Dockerfile.auth
- Detects environment (production/staging) from branch
- Sets
AUTH_MASTER_KEY from GitHub Secrets
Required GitHub Secrets
| Secret | Used by |
|---|
RAILWAY_TOKEN | All deploy jobs |
AUTH_MASTER_KEY | deploy-auth job |
Railway Service Configuration
The auth service should be configured as a separate Railway service with:
- Custom start command:
python scripts/migrate.py && uvicorn src.auth_service.main:create_app --factory --host 0.0.0.0 --port 8001 --workers 2
- Health check:
GET /health
- Private networking enabled (only API service needs to reach it)
Bootstrap Script
For existing deployments migrating from X-API-Key auth:
python scripts/bootstrap_auth.py
This script:
- Reads all customers from
public.customers
- For each customer:
- Generates an ES256 key pair
- Encrypts the private key with AES-256-GCM
- Stores the signing key in
auth.signing_keys
- Creates an app token in
auth.app_tokens
- Outputs the raw app token (for customer distribution)
Run this script once after deploying the auth schema. The output includes raw app tokens — distribute them securely to customers.