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

Use Node.js 24 LTS for source installs, builds and tests, and `npm ci` with the
committed lockfiles. Vitest 5 requires Node.js 22.12 or newer. The auth-service
Dockerfile supplies its own pinned Node 26 runtime. These development-tooling
requirements do not change the versions already published to SDK registries.

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

This table is a quick-start subset, not an exhaustive schema. Consult
`apps/auth-service/src/config.ts` and `apps/auth-service/.env.example` from the
exact release you deploy for all feature-specific settings and validation
rules.

| Variable | Required | Default | Description |
| - | - | - | - |
| `DATABASE_URL` | Yes | — | PostgreSQL connection string |
| `REDIS_URL` | Yes | — | Redis connection string |
| `JWT_SIGNING_ALG` | No | `RS256` | Signing algorithm: `RS256` or `ES256` |
| `RSA_PRIVATE_KEY` | Yes\* | — | PKCS#8 PEM RSA private key (RS256). \*Or `EC_PRIVATE_KEY` for ES256, `SIGNING_KEY_STORE=postgres`, or `AUTO_GENERATE_KEYS=true` (dev only) |
| `EC_PRIVATE_KEY` | Yes\* | — | PKCS#8 PEM EC P-256 private key (ES256) |
| `JWT_VERIFICATION_PUBLIC_KEYS` | No | — | JWK Set of public keys published for verification only (a key about to sign, or one that no longer signs) |
| `JWT_LEGACY_KID_KEY` | No | the `RSA_PRIVATE_KEY` key | Thumbprint `kid` of the RSA key that signed pre-0.6 tokens (kid `grantex-YYYY-MM`) |
| `SIGNING_KEY_STORE` | No | `env` | `env` or `postgres` (stored encrypted, rotated publish-then-sign with `dist/cli/rotate-signing-key.js`) |
| `AUTO_GENERATE_KEYS` | No | `false` | Auto-generate the signing key 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.).

The repository currently contains ordered migrations through `092`, including
`091_agent_prepaid_wallets.sql` for wallet balances, assignments,
reservations, reloads, controls, and append-only ledger evidence, and
`092_layered_wallet_spend_controls.sql` for safe assignment defaults, layered
policy, exact payment approval, reload velocity controls, and policy-decision
evidence. Inspect the migration directory in the exact release you deploy
rather than relying on a copied file count.

To upgrade, just restart the service — new migration files are applied automatically.

## 7. Key Rotation

The JWK Set publishes every platform signing key with its thumbprint `kid`, `alg` and `use: "sig"`,
plus the RSA key under the pre-0.6 `grantex-YYYY-MM` kids, so tokens issued before 0.6 keep verifying.
No rotation step invalidates an outstanding token.

* **Postgres key store** (`SIGNING_KEY_STORE=postgres`): the first start imports the env keys. Run
  `node dist/cli/rotate-signing-key.js [--alg ES256]`; the new key is published first and signs after
  `SIGNING_KEY_ACTIVATION_DELAY_SECONDS`. The old key stays published for `SIGNING_KEY_RETIRED_GRACE_SECONDS`.
* **Env key store**: publish the new public key in `JWT_VERIFICATION_PUBLIC_KEYS`, wait, switch the
  private key while keeping the old public key there (and `JWT_LEGACY_KID_KEY` for the old RSA key),
  then remove the old key after its tokens expire. See `docs/self-hosting.md` Section 7.

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

### Prepaid wallets and x402

Prepaid-wallet hosting adds external dependencies that Docker, Helm, and the
auth-service cannot provision automatically. Before enabling the feature:

* route the exact public `/v1/prepaid-wallets` audience and wildcard path to
  the auth service over TLS;
* keep `external` custody disabled until a provider adapter verifies funding,
  reservation, settlement, reconciliation, duplicate events, and recovery;
* operate an SSE/WebSocket bridge if reload events must reach a human through
  email, SMS, or messaging;
* require merchants to persist side-effect results under HTTP
  `Idempotency-Key` after successful settlement;
* verify the exact SDK and x402 versions on npm rather than inferring
  publication from repository manifests.

Follow [Prepaid Wallet Production Readiness](/guides/prepaid-wallet-production)
for the complete route list, dependency matrix, failure boundaries, deployment
probes, and PowerShell package-release procedure.

## Ownership

Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.