1. Quick Start (Dev)
git clone https://github.com/mishrasanjeev/grantex.git
cd grantex
docker compose up --build
This starts PostgreSQL, Redis, and the auth service. Two developer accounts are seeded automatically:
| Account | API key | Mode |
|---|
| Live | dev-api-key-local | Normal consent flow |
| Sandbox | sandbox-api-key-local | Auto-approves grants, returns code immediately |
Verify it’s running:
curl http://localhost:3001/health
# { "status": "ok" }
curl http://localhost:3001/.well-known/jwks.json
# { "keys": [{ "kty": "RSA", "alg": "RS256", ... }] }
The dev compose exposes database and Redis ports and uses hardcoded credentials. Never use it in production.
2. Generating a Production RSA Key
Grantex signs grant tokens with RSA-256. Generate a 2048-bit private key:
openssl genrsa -out private.pem 2048
Collapse to a single line for environment variables:
awk 'NF {sub(/\r/, ""); printf "%s\\n", $0}' private.pem
Copy the output and use it as RSA_PRIVATE_KEY.
Keep private.pem out of source control. The JWKS endpoint exposes only the public key.
3. Production Docker Compose
Prerequisites
- Docker 24+ with Compose v2
- A domain name with DNS pointing to your server
- TLS certificate (Let’s Encrypt for production)
Step 1 — Fill in the env file
cp .env.prod.example .env.prod
Edit .env.prod and replace every change-me-* placeholder. Set RSA_PRIVATE_KEY to the collapsed PEM and JWT_ISSUER to your public base URL.
Step 2 — Provide TLS certificates
# Let's Encrypt (production)
certbot certonly --standalone -d auth.example.com
cp /etc/letsencrypt/live/auth.example.com/fullchain.pem deploy/nginx/certs/server.crt
cp /etc/letsencrypt/live/auth.example.com/privkey.pem deploy/nginx/certs/server.key
Step 3 — Start the stack
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d
Architecture:
Internet → nginx (:443) → auth-service:3001
↓
postgres + redis (internal network only)
4. Kubernetes / Helm
Prerequisites
- Kubernetes 1.26+, Helm 3.x
- Managed PostgreSQL and Redis
- An RSA private key (Section 2)
Install
helm install grantex deploy/helm/grantex/ \
--namespace grantex --create-namespace \
--set externalDatabase.url="postgres://user:pass@host:5432/grantex" \
--set externalRedis.url="redis://:pass@host:6379" \
--set rsaPrivateKey="$(awk 'NF {sub(/\r/, ""); printf "%s\\n", $0}' private.pem)" \
--set config.jwtIssuer="https://auth.example.com"
Enable Ingress
helm upgrade grantex deploy/helm/grantex/ \
--reuse-values \
--set ingress.enabled=true \
--set ingress.className=nginx \
--set "ingress.hosts[0].host=auth.example.com" \
--set "ingress.hosts[0].paths[0].path=/" \
--set "ingress.hosts[0].paths[0].pathType=Prefix" \
--set "ingress.tls[0].secretName=grantex-tls" \
--set "ingress.tls[0].hosts[0]=auth.example.com"
Use an existing Secret
kubectl create secret generic grantex-secrets \
--namespace grantex \
--from-literal=RSA_PRIVATE_KEY="$(cat private.pem)"
helm install grantex deploy/helm/grantex/ \
--namespace grantex \
--set existingSecret=grantex-secrets \
--set externalDatabase.url="..." \
--set externalRedis.url="..."
5. Environment Variable Reference
| Variable | Required | Default | Description |
|---|
DATABASE_URL | Yes | — | PostgreSQL connection string |
REDIS_URL | Yes | — | Redis connection string |
RSA_PRIVATE_KEY | Yes* | — | PEM private key for JWT signing. *Or set AUTO_GENERATE_KEYS=true (dev only) |
AUTO_GENERATE_KEYS | No | false | Auto-generate RSA keypair at startup (dev only) |
JWT_ISSUER | Yes | https://grantex.dev | iss claim in every JWT |
PORT | No | 3001 | Port the auth service listens on |
HOST | No | 0.0.0.0 | Bind address |
SEED_API_KEY | No | — | Pre-seed a live developer API key (dev only) |
SEED_SANDBOX_KEY | No | — | Pre-seed a sandbox API key (dev only) |
STRIPE_SECRET_KEY | No | — | Enable Stripe billing integration |
STRIPE_WEBHOOK_SECRET | No | — | Stripe webhook signature validation |
STRIPE_PRICE_PRO | No | — | Stripe price ID for Pro tier |
STRIPE_PRICE_ENTERPRISE | No | — | Stripe price ID for Enterprise tier |
6. Database Migrations
Migrations run automatically on every startup. The auth service reads all *.sql files from the migrations/ directory and executes each one using idempotent DDL (CREATE TABLE IF NOT EXISTS, etc.).
There are currently 9 migration files covering: core tables, webhooks, consent, delegation, compliance, policies, anomalies, SCIM/SSO, and developer email.
To upgrade, just restart the service — new migration files are applied automatically.
7. Key Rotation
- Generate a new RSA key pair (Section 2)
- Update
RSA_PRIVATE_KEY in your env file or Kubernetes secret
- Restart the auth service
Tokens signed with the old key remain valid until expiry because the JWKS endpoint always exposes the current public key — clients re-fetch it automatically when verification fails.
8. Health Checks & Monitoring
GET /health → 200 { "status": "ok" }
All logs are emitted as JSON to stdout, compatible with Datadog, Loki, and CloudWatch Logs.
9. Backup & Recovery
PostgreSQL
docker compose -f docker-compose.prod.yml exec postgres \
pg_dump -U "$POSTGRES_USER" grantex | gzip > "grantex-$(date +%Y%m%d).sql.gz"
Redis
Redis holds ephemeral token metadata and rate-limiting state. If Redis data is lost, in-flight auth requests will fail temporarily, but no permanent data is lost. PostgreSQL is the source of truth.
10. Production Readiness Checklist