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:


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_KEY for 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:

  1. Set SELF_HOSTED=true — plan enforcement switches to the global license key
  2. Set LICENSE_KEY=<jwt> — issued by the Licensing Portal
  3. Per-org subscription plan selection is hidden from the Admin Console
  4. 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

  1. Check that the OAuth provider credentials (GOOGLE_CLIENT_ID, etc.) are set in the backend env
  2. Ensure the org's plan allows the social provider (check planService in backend logs)
  3. 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

Subscribe to The Fabrixly Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe