> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-Hosting

> Run your own Grantex auth service — from local dev to production Kubernetes.

## 1. Quick Start (Dev)

```bash theme={null}
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:

```bash theme={null}
curl http://localhost:3001/health
# { "status": "ok" }

curl http://localhost:3001/.well-known/jwks.json
# { "keys": [{ "kty": "RSA", "alg": "RS256", ... }] }
```

<Warning>
  The dev compose exposes database and Redis ports and uses hardcoded credentials. Never use it in production.
</Warning>

## 2. Generating a Production RSA Key

Grantex signs grant tokens with RSA-256. Generate a 2048-bit private key:

```bash theme={null}
openssl genrsa -out private.pem 2048
```

Collapse to a single line for environment variables:

```bash theme={null}
awk 'NF {sub(/\r/, ""); printf "%s\\n", $0}' private.pem
```

Copy the output and use it as `RSA_PRIVATE_KEY`.

<Note>
  Keep `private.pem` out of source control. The JWKS endpoint exposes only the public key.
</Note>

## 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

```bash theme={null}
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

```bash theme={null}
# 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

```bash theme={null}
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

```bash theme={null}
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

```bash theme={null}
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

```bash theme={null}
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

1. Generate a new RSA key pair (Section 2)
2. Update `RSA_PRIVATE_KEY` in your env file or Kubernetes secret
3. 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

```bash theme={null}
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

* [ ] `RSA_PRIVATE_KEY` is a real 2048-bit RSA key
* [ ] `POSTGRES_PASSWORD` and `REDIS_PASSWORD` are strong random values
* [ ] `SEED_API_KEY` and `SEED_SANDBOX_KEY` are **not** set
* [ ] TLS is enabled end-to-end
* [ ] Database and Redis ports are not exposed publicly
* [ ] `JWT_ISSUER` matches your public base URL exactly
* [ ] Automated database backups are configured
* [ ] Health checks are wired into your load balancer
* [ ] CPU and memory limits are set
* [ ] Log forwarding is configured
