Fabrixly Identity Server — Complete Setup Guide
Fabrixly Identity Server — Complete Setup Guide
This guide covers every deployment method for the Fabrixly Identity Server (IDS) and the Fabrixly Licensing Portal:
- Local Development — run directly with Node.js
- Docker Compose — quickest production-like setup
- Kubernetes / Helm — production-grade cluster deployment
Architecture Overview
┌─────────────────────────────────────────────────┐
│ Fabrixly Identity Server │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Backend │ │ Console │ │ Test Client │ │
│ │ :3000 │ │ :80 │ │ :3001 │ │
│ └──────────┘ └──────────┘ └───────────────┘ │
│ ↕ all share PostgreSQL │
│ ┌───────────────────────────────────────────┐ │
│ │ PostgreSQL :5432 │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Fabrixly Licensing Portal │
│ ┌──────────────────────────────────────────┐ │
│ │ licensing-portal :3005 │ │
│ │ (uses IDS as OIDC provider) │ │
│ └──────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────┐ │
│ │ PostgreSQL :5432 (separate volume) │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Important: The Licensing Portal is deployed separately from the IDS. It authenticates via IDS using OIDC, but runs in its own container/namespace.
Docker Image Tags
All images are published to Docker Hub under kumaravinit/zero:
| Image | Latest Tag | Versioned Tag |
|---|---|---|
| IDS Backend | ids-backend-latest |
ids-backend-1.0.1 |
| IDS Console | ids-console-latest |
ids-console-1.0.1 |
| Test Client | ids-test-client-latest |
ids-test-client-1.0.1 |
| Licensing Portal | licensing-portal-latest |
licensing-portal-1.0.1 |
1. Local Development
Prerequisites
- Node.js 18+
- PostgreSQL 15+ running locally
Steps
# Clone the repository
git clone https://github.com/your-org/fabrixly-identity-server.git
cd fabrixly-identity-server
# Install dependencies
npm install
# Copy env file
cp .env.example .env
# Edit .env — minimum required:
# DB_HOST=localhost
# DB_USERNAME=admin
# DB_PASSWORD=admin123
# SESSION_SECRET=<random-32-char-string>
# JWT_SECRET=<random-32-char-string>
# ISSUER=http://localhost:3000/oidc
# Run (seeds automatically in development)
npm run dev
The server seeds the system admin, countries, and default plans automatically in development mode.
2. Docker Compose
2a. Fabrixly Identity Server
The IDS compose file brings up 4 services: PostgreSQL, Backend, Console, and Test Client.
Step 1 — Create your .env file
cd fabrixly-identity-server
cp .env.example .env
Edit .env and set at minimum:
# ── Required secrets ─────────────────────────────────────────────────
SESSION_SECRET=replace-me-with-32-char-random-string
JWT_SECRET=replace-me-with-32-char-random-string
# ── Database ──────────────────────────────────────────────────────────
DB_USERNAME=admin
DB_PASSWORD=admin123
DB_NAME=identity_server
# ── Server ────────────────────────────────────────────────────────────
ISSUER=http://localhost:3000/oidc
# ── Email (required for OTP/invite flows) ─────────────────────────────
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=SG.your-sendgrid-api-key
SMTP_FROM="Fabrixly IDS" <no-reply@your-domain.com>
# ── Admin account (seeded on first start) ─────────────────────────────
SYSTEM_ADMIN_EMAIL=admin@system.com
SYSTEM_ADMIN_PASSWORD=admin123
# ── Auto-seed on first production run ─────────────────────────────────
AUTO_SEED=true
# ── Self-Hosting / Licensing ──────────────────────────────────────────
SELF_HOSTED=false
LICENSE_KEY=
Step 2 — Generate secure secrets
echo "SESSION_SECRET=$(openssl rand -base64 32)"
echo "JWT_SECRET=$(openssl rand -base64 32)"
Paste the output into your .env file.
Step 3 — Start the stack
docker compose up -d
This starts:
- PostgreSQL on port
5432 - Backend (IDS) on port
3000 - Console (Admin UI) on port
80 - Test Client on port
3001
Step 4 — Seed the database
The backend seeds automatically on first boot if AUTO_SEED=true is set. This creates:
- System admin account (
SYSTEM_ADMIN_EMAIL/SYSTEM_ADMIN_PASSWORD) - Default plans (Free, Pro, Enterprise)
- Country seed data
The seed is idempotent — safe to run multiple times; it won't duplicate data.
To force a re-seed manually:
docker compose exec backend sh -c "node -e \"require('./dist/seeds').runAll()\""
Step 5 — Verify
# Check all containers are running
docker compose ps
# Tail logs
docker compose logs -f backend
# Access
open http://localhost # Admin Console
open http://localhost:3000 # Identity Server API (local dev only — production: https://ids.fabrixly.com)
open http://localhost:3001 # Test Client
Login with: admin@system.com / admin123 (or your custom values).
Step 6 — Pin to a specific version
The default docker-compose.yml uses 1.0.0 tags. To use 1.0.1:
Create docker-compose.override.yml alongside the main file:
version: '3.8'
services:
backend:
image: kumaravinit/zero:ids-backend-1.0.1
console:
image: kumaravinit/zero:ids-console-1.0.1
test-client:
image: kumaravinit/zero:ids-test-client-1.0.1
Then run:
docker compose up -d
# Docker Compose automatically merges override files
2b. Fabrixly Licensing Portal
The Licensing Portal is deployed independently. It authenticates users via the running IDS.
Prerequisites
- IDS already running and accessible. Local dev default:
http://localhost:3000. Production:https://ids.fabrixly.com - An RSA
PRIVATE_KEYfor signing license JWTs (see below)
Step 1 — Navigate to the Licensing Portal repo
cd ../zero-licencing-portal
Step 2 — Generate the PRIVATE_KEY
The licensing portal signs license tokens with an RSA private key:
node -e "
const crypto = require('crypto');
const { privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
process.stdout.write(privateKey);
" > private.pem
cat private.pem
Save this key securely — you'll need it every time you deploy.
Step 3 — Create .env file
cat > .env << EOF
PORT=3005
ISSUER=http://localhost:3000/oidc
CLIENT_ID=licensing-portal-client
CLIENT_SECRET=licensing_portal_secret
REDIRECT_URI=http://localhost:3005/callback
SESSION_SECRET=$(openssl rand -base64 32)
DATABASE_URL=postgresql://admin:admin123@postgres:5432/identity_server
IDS_BASE_URL=http://host.docker.internal:3000
IDS_ADMIN_EMAIL=admin@system.com
IDS_ADMIN_PASSWORD=admin123
PRIVATE_KEY=$(cat private.pem)
EOF
Note: If running IDS via Docker Compose on the same machine, use host.docker.internal:3000 (Mac/Windows) or the IDS container IP to connect from within the licensing portal container.Step 4 — Start the Licensing Portal
docker compose up -d
Or run with the pre-built image (skip build):
docker compose -f docker-compose.yml up -d
# The compose file references `image: zero-licencing-portal:latest` by default
To use the published image:
# Edit docker-compose.yml: replace `build:` block with:
# image: kumaravinit/zero:licensing-portal-1.0.1
docker compose up -d
Step 5 — Verify
docker compose ps
docker compose logs -f licensing-portal
open http://localhost:3005
3. Kubernetes / Helm
3a. Fabrixly Identity Server
Prerequisites
# Helm 3 + kubectl configured
helm version
kubectl cluster-info
# Install Helm dependencies (PostgreSQL subchart)
cd helm/fabrixly-ids
helm dependency update
cd ../..
Step 1 — Create namespace
kubectl create namespace fabrixly-ids
Step 2 — Create secrets
SESSION_SECRET=$(openssl rand -base64 32)
JWT_SECRET=$(openssl rand -base64 32)
DB_PASSWORD=$(openssl rand -base64 24)
PG_ADMIN_PASSWORD=$(openssl rand -base64 24)
cat > helm/fabrixly-ids/values-secrets-production.yaml << EOF
backend:
secrets:
SESSION_SECRET: "${SESSION_SECRET}"
JWT_SECRET: "${JWT_SECRET}"
DB_PASSWORD: "${DB_PASSWORD}"
SMTP_USER: "your-smtp-user-or-apikey"
SMTP_PASS: "your-smtp-password-or-apikey"
postgresql:
auth:
password: "${DB_PASSWORD}"
postgresPassword: "${PG_ADMIN_PASSWORD}"
EOF
# Keep this out of git
echo "values-secrets-production.yaml" >> helm/fabrixly-ids/.gitignore
Step 3 — Create environment values file
Create helm/fabrixly-ids/values-myenv.yaml:
global:
domain: your-ids-domain.com
backend:
image:
repository: kumaravinit/zero
tag: "ids-backend-1.0.1"
env:
NODE_ENV: production
ISSUER: "https://your-ids-domain.com/oidc"
DB_HOST: "postgresql-service"
DB_PORT: "5432"
DB_NAME: "identity_server"
DB_SYNC: "false"
DB_LOGGING: "false"
SMTP_HOST: "smtp.sendgrid.net"
SMTP_PORT: "587"
SMTP_SECURE: "true"
SMTP_FROM: "noreply@your-ids-domain.com"
CONSOLE_URL: "https://your-ids-domain.com"
AUTO_SEED: "true"
SELF_HOSTED: "false"
LICENSE_KEY: ""
console:
image:
repository: kumaravinit/zero
tag: "ids-console-1.0.1"
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: your-ids-domain.com
paths:
- path: /(api|oidc|interaction|password-reset|verification|legal)(/|$)(.*)
pathType: ImplementationSpecific
backend: backend
- path: /
pathType: Prefix
backend: console
tls:
- secretName: fabrixly-ids-tls
hosts:
- your-ids-domain.com
Step 4 — Install
helm install fabrixly-ids ./helm/fabrixly-ids \
-f helm/fabrixly-ids/values-myenv.yaml \
-f helm/fabrixly-ids/values-secrets-production.yaml \
--namespace fabrixly-ids \
--wait --timeout 5m
Step 5 — Seed (if AUTO_SEED not set)
BACKEND_POD=$(kubectl get pod -n fabrixly-ids -l app=fabrixly-ids-backend -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n fabrixly-ids $BACKEND_POD -- sh -c "AUTO_SEED=true node dist/index.js"
Or set AUTO_SEED: "true" in the values and do a rolling restart:
helm upgrade fabrixly-ids ./helm/fabrixly-ids \
-f helm/fabrixly-ids/values-myenv.yaml \
-f helm/fabrixly-ids/values-secrets-production.yaml \
--set backend.env.AUTO_SEED=true \
--namespace fabrixly-ids
Step 6 — Upgrade
helm upgrade fabrixly-ids ./helm/fabrixly-ids \
-f helm/fabrixly-ids/values-myenv.yaml \
-f helm/fabrixly-ids/values-secrets-production.yaml \
--namespace fabrixly-ids \
--set backend.image.tag=ids-backend-1.0.1 \
--set console.image.tag=ids-console-1.0.1
Step 7 — Verify
kubectl get pods -n fabrixly-ids
kubectl get svc -n fabrixly-ids
kubectl get ingress -n fabrixly-ids
kubectl logs -f deployment/fabrixly-ids-backend -n fabrixly-ids
3b. Fabrixly Licensing Portal
Step 1 — Create namespace and secrets
kubectl create namespace zero-licensing
# Generate RSA key
node -e "
const crypto = require('crypto');
const { privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
process.stdout.write(privateKey);
" > /tmp/licensing-private.pem
kubectl create secret generic licensing-portal-secrets \
--from-literal=SESSION_SECRET="$(openssl rand -base64 32)" \
--from-literal=IDS_ADMIN_PASSWORD="your-ids-admin-password" \
--from-file=PRIVATE_KEY=/tmp/licensing-private.pem \
--namespace zero-licensing
# Clean up temp key file
rm /tmp/licensing-private.pem
Step 2 — Create values file
cd ../zero-licencing-portal
Create helm/zero-licencing-portal/values-production.yaml:
replicaCount: 1
image:
repository: kumaravinit/zero
tag: "licensing-portal-1.0.1"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 3005
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: licensing.your-domain.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: licensing-portal-tls
hosts:
- licensing.your-domain.com
env:
nodeEnv: production
port: 3005
issuer: "https://your-ids-domain.com/oidc"
clientId: "licensing-portal-client"
redirectUri: "https://licensing.your-domain.com/callback"
# IDS running in fabrixly-ids namespace — cluster-internal DNS
idsBaseUrl: "http://fabrixly-ids-backend.fabrixly-ids.svc.cluster.local:3000"
idsAdminEmail: "admin@system.com"
secrets:
sessionSecret: "" # injected from K8s secret
databaseUrl: "postgresql://admin:password@fabrixly-ids-postgresql.fabrixly-ids.svc.cluster.local:5432/licensing_db"
idsAdminPassword: "" # injected from K8s secret
privateKey: "" # injected from K8s secret
Step 3 — Install
helm install zero-licensing ./helm/zero-licencing-portal \
-f helm/zero-licencing-portal/values-production.yaml \
--namespace zero-licensing \
--wait --timeout 3m
Step 4 — Verify
kubectl get pods -n zero-licensing
kubectl logs -f deployment/zero-licensing-licensing-portal -n zero-licensing
open https://licensing.your-domain.com
4. Build & Push Images
Prerequisites
# Login to Docker Hub
docker login -u kumaravinit
Build and push all images (latest + versioned)
cd fabrixly-identity-server
chmod +x scripts/utils/build-and-push-latest.sh
./scripts/utils/build-and-push-latest.sh
This script builds and pushes all four images with both latest and 1.0.1 tags.
Bump the version
Edit line 6 of scripts/utils/build-and-push-latest.sh:
VERSION="1.0.2" # ← bump this
Then re-run the script.
5. Self-Hosted Mode
When enabling self-hosting for customers:
- Set
SELF_HOSTED=true— plan enforcement switches to the global license key - Set
LICENSE_KEY=<jwt>— issued by the Licensing Portal - Per-org subscription plan selection is hidden from the Admin Console
- Org limits (users, M2M tokens, social providers) are read from the license JWT
# .env (IDS backend)
SELF_HOSTED=true
LICENSE_KEY=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
In Helm:
backend:
env:
SELF_HOSTED: "true"
LICENSE_KEY: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
6. Environment Variable Reference
IDS Backend
| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV |
Yes | development |
development or production |
PORT |
No | 3000 |
HTTP listen port |
ISSUER |
Yes | — | Full OIDC issuer URL (must end with /oidc) |
SESSION_SECRET |
Yes | — | Min 32-char secret for sessions |
JWT_SECRET |
Yes | — | Min 32-char secret for JWTs |
DB_HOST |
Yes | localhost |
PostgreSQL hostname |
DB_PORT |
No | 5432 |
PostgreSQL port |
DB_USERNAME |
Yes | — | PostgreSQL user |
DB_PASSWORD |
Yes | — | PostgreSQL password |
DB_NAME |
Yes | — | PostgreSQL database name |
DB_SYNC |
No | false |
Auto-sync schema — never true in production |
SMTP_HOST |
Yes | — | SMTP hostname |
SMTP_PORT |
No | 587 |
SMTP port |
SMTP_USER |
Yes | — | SMTP user / API key |
SMTP_PASS |
Yes | — | SMTP password |
SMTP_FROM |
Yes | — | Sender email address |
SYSTEM_ADMIN_EMAIL |
No | admin@system.com |
Seeded admin email |
SYSTEM_ADMIN_PASSWORD |
No | admin123 |
Seeded admin password |
AUTO_SEED |
No | false |
Seed DB on startup if true (idempotent) |
SELF_HOSTED |
No | false |
Enable self-hosted license mode |
LICENSE_KEY |
No | — | License JWT (required when SELF_HOSTED=true) |
GOOGLE_CLIENT_ID |
No | — | Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
No | — | Google OAuth client secret |
GITHUB_CLIENT_ID |
No | — | GitHub OAuth App client ID |
GITHUB_CLIENT_SECRET |
No | — | GitHub OAuth App client secret |
Licensing Portal
| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
No | 3005 |
HTTP listen port |
ISSUER |
Yes | — | IDS OIDC issuer URL |
CLIENT_ID |
Yes | — | OIDC client ID registered in IDS |
CLIENT_SECRET |
Yes | — | OIDC client secret |
REDIRECT_URI |
Yes | — | OAuth2 callback URL |
SESSION_SECRET |
Yes | — | Session encryption secret |
DATABASE_URL |
Yes | — | PostgreSQL connection string |
IDS_BASE_URL |
Yes | — | IDS API base URL (no /oidc suffix) |
IDS_ADMIN_EMAIL |
Yes | — | IDS system admin email |
IDS_ADMIN_PASSWORD |
Yes | — | IDS system admin password |
PRIVATE_KEY |
Yes | — | RSA PKCS#8 PEM private key for signing licenses |
7. Troubleshooting
Backend crashes immediately
Check PostgreSQL is ready and reachable:
docker compose logs postgres
docker compose restart backend
AUTO_SEED not running
Verify the env var is passed through:
docker compose exec backend printenv AUTO_SEED
Social login returns Configuration Error
- Check that the OAuth provider credentials (
GOOGLE_CLIENT_ID, etc.) are set in the backend env - Ensure the org's plan allows the social provider (check
planServicein backend logs) - Verify the OAuth callback URL is whitelisted in the provider's developer console
Licensing portal can't connect to IDS
From inside the licensing portal container, test connectivity:
docker compose exec licensing-portal wget -qO- http://host.docker.internal:3000/api/health
If unreachable, set IDS_BASE_URL to the correct host/port.
License key is invalid at IDS
The PRIVATE_KEY used in the Licensing Portal to sign licenses must correspond to the public key embedded in the IDS to verify them. Regenerate consistently.
Quick-Start Cheatsheet
# 1. Clone IDS
git clone https://github.com/your-org/fabrixly-identity-server.git
cd fabrixly-identity-server
# 2. Generate secrets
echo "SESSION_SECRET=$(openssl rand -base64 32)" > .env
echo "JWT_SECRET=$(openssl rand -base64 32)" >> .env
echo "AUTO_SEED=true" >> .env
echo "DB_USERNAME=admin" >> .env
echo "DB_PASSWORD=admin123" >> .env
echo "ISSUER=http://localhost:3000/oidc" >> .env
# 3. Start IDS
docker compose up -d
# 4. Watch logs until ready
docker compose logs -f backend
# 5. Open Admin Console
open http://localhost
# Credentials: admin@system.com / admin123