Identity Service — Local Dev Setup
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · DEPLOYMENT_TOPOLOGY
1. Prerequisites
| Tool | Minimum version | Install |
|---|---|---|
| Node.js | 22 LTS | nvm install 22 |
| pnpm | 9.x | npm i -g pnpm |
| Docker Desktop | 4.x | docker.com |
| Docker Compose | V2 plugin | bundled with Docker Desktop |
2. docker compose stack
docker-compose.identity.yml (in repo root infra/local/):
version: "3.9"
services:
identity-postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: identity
POSTGRES_PASSWORD: identity_dev
POSTGRES_DB: identity_db
ports: ["5432:5432"]
volumes:
- identity-pgdata:/var/lib/postgresql/data
- ./init/identity:/docker-entrypoint-initdb.d # runs schema migrations
identity-redis:
image: redis:7-alpine
ports: ["6379:6379"]
nats:
image: nats:2.10-alpine
args: ["-js", "-m", "8222"]
ports: ["4222:4222", "8222:8222"]
keycloak-dev:
image: quay.io/keycloak/keycloak:24.0
command: start-dev
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
ports: ["8080:8080"]
volumes:
identity-pgdata:
Start the stack:
docker compose -f infra/local/docker-compose.identity.yml up -d
3. Service environment (.env.example)
Copy services/identity-service/.env.example → .env.local and fill:
DATABASE_URL=postgresql://identity:identity_dev@localhost:5432/identity_db
DATABASE_REPLICA_URL=postgresql://identity:identity_dev@localhost:5432/identity_db
REDIS_URL=redis://localhost:6379
NATS_URL=nats://localhost:4222
IDENTITY_JWT_ISSUER=http://localhost:3001
IDENTITY_JWT_AUDIENCE=ghasi-ehealth-local
# Dev: use local software KMS mock (localstack or fake-kms)
IDENTITY_KMS_KEY_ARN=arn:aws:kms:us-east-1:000000000000:key/local-dev-key
IDENTITY_DEVICE_CERT_KEY_ARN=arn:aws:kms:us-east-1:000000000000:key/local-dev-device-key
IDENTITY_KEYCLOAK_URL=http://localhost:8080
IDENTITY_SESSION_ABSOLUTE_TTL_S=28800
IDENTITY_ARGON2_MEMORY_KIB=65536
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_SERVICE_NAME=identity-service
For local KMS, use LocalStack or the fake-kms Docker image and seed keys with aws kms create-key --endpoint-url http://localhost:4566.
4. Run the service
cd services/identity-service
# Install dependencies (from monorepo root)
pnpm install
# Run database migrations
pnpm drizzle-kit push:pg
# Seed module catalogue
pnpm run seed:modules
# Start in watch mode
pnpm dev
Service listens on http://localhost:3001.
JWKS: http://localhost:3001/.well-known/jwks.json
5. Seed data
pnpm run seed:modules inserts the following always-on module catalogue entries:
| Code | Name |
|---|---|
platform.iam | Platform IAM |
platform.audit | Platform Audit |
ehr.core | EHR Core |
Additional seed scripts:
pnpm run seed:dev-tenant— creates aten_DEV00000000001test tenant and admin user.pnpm run seed:licenses— assigns always-on licenses to the dev tenant root node.
6. Common commands
| Command | Purpose |
|---|---|
pnpm dev | Start with hot reload |
pnpm test | Run unit tests |
pnpm test:integration | Run integration tests (requires docker compose stack) |
pnpm test:cov | Coverage report |
pnpm drizzle-kit generate:pg | Generate migration from schema changes |
pnpm drizzle-kit push:pg | Apply migrations to local DB |
pnpm lint | ESLint |
pnpm typecheck | tsc --noEmit |
7. Useful local endpoints
| Endpoint | Description |
|---|---|
POST http://localhost:3001/api/v1/auth/login | Login (email + password) |
GET http://localhost:3001/.well-known/jwks.json | Local JWKS |
GET http://localhost:3001/api/v1/licensing/modules | Module catalogue |
GET http://localhost:3001/health | Health check |
http://localhost:8080 | Keycloak admin console (admin / admin) |
http://localhost:8222 | NATS monitoring |
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
KMS key not found | LocalStack not running or key not seeded | Start LocalStack; run pnpm run seed:kms |
argon2 ENOMEM | Container memory limit too low | Raise Docker Desktop memory limit to ≥ 4 GB |
NATS connection refused | NATS container not healthy | docker compose logs nats; check JetStream flag |
RLS error: app.tenant_id not set | Missing tenant context in test | Pass tenantId header or use identity_rls_bypass role in tests |