A Multi-tenant OIDC Provider built on node-oidc-provider
4.0K
Multi-tenant OpenID Connect provider built on node-oidc-provider + Koa + TypeScript. Provides organization-scoped authentication, user management, RBAC, custom claims, two-factor authentication, and a comprehensive admin CLI.
Porta requires PostgreSQL and Redis as companion services. The fastest way to get started is with Docker Compose.
Get Porta running in under 5 minutes. No git clone required — just create two files and run.
Fastest path The installer generates
docker-compose.ymland.env, starts the stack, and applies migrations. It scans for a free host port if you do not choose one.curl -fsSL https://raw.githubusercontent.com/blendsdk/porta-identity/main/install-porta.sh | bashThe manual steps below describe the same deployment file by file.
docker-compose.ymlCreate a file called docker-compose.yml with the following content:
services:
# ── Porta OIDC Provider ─────────────────────
porta:
image: blendsdk/porta:latest
container_name: porta-app
restart: unless-stopped
ports:
- '${PORT:-3000}:3000'
env_file:
- .env
environment:
DATABASE_URL: postgresql://porta:${POSTGRES_PASSWORD}@postgres:5432/porta
REDIS_URL: redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 5s
start_period: 30s
retries: 3
# ── PostgreSQL 16 ───────────────────────────
postgres:
image: postgres:16-alpine
container_name: porta-postgres
restart: unless-stopped
environment:
POSTGRES_DB: porta
POSTGRES_USER: porta
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- porta_pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U porta']
interval: 5s
timeout: 5s
retries: 5
# ── Redis 7 ─────────────────────────────────
redis:
image: redis:7-alpine
container_name: porta-redis
restart: unless-stopped
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 5s
retries: 5
volumes:
porta_pgdata:
driver: local
.envCreate a .env file in the same directory:
# Server
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
# Database password (used by both Porta and PostgreSQL)
POSTGRES_PASSWORD=<replace-with-a-random-database-password>
# OIDC issuer — change to your public-facing URL
ISSUER_BASE_URL=https://auth.example.com
# Cookie signing key — CHANGE THIS in production!
COOKIE_KEYS=CHANGE-ME-to-a-random-string-at-least-32-chars
# Email (configure SMTP for magic links, invitations, password reset)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=<smtp-user>
SMTP_PASS=<smtp-password>
[email protected]
# Logging
LOG_LEVEL=info
# Two-factor encryption root key — required in production
TWO_FACTOR_ENCRYPTION_KEY=<replace-with-exactly-64-hex-characters>
# Signing-key encryption root key — required in production
SIGNING_KEY_ENCRYPTION_KEY=<replace-with-a-different-64-hex-character-value>
# Reverse proxy — set to "true" when behind a TLS-terminating proxy
# TRUST_PROXY=false
# Apply migrations as a controlled deployment step
PORTA_AUTO_MIGRATE=false
docker compose up -d --wait postgres redis
docker compose run --rm porta node dist/cli/index.js migrate up
docker compose up -d porta
This starts PostgreSQL and Redis, applies pending migrations as a controlled step, and then starts
Porta. Keep PORTA_AUTO_MIGRATE=false for normal production operation.
Wait a few seconds for startup, then check:
curl http://localhost:3000/health
You should see:
{ "status": "ok", "checks": { "database": "ok", "redis": "ok" } }
docker exec -it porta-app porta init
This interactive command creates:
Or run it non-interactively:
docker exec porta-app porta init \
--email [email protected] \
--given-name Admin \
--family-name User \
--password 'YourSecurePassword123!'
💡 Standalone CLI For full admin management, install the standalone CLI on your workstation:
npm install -g @portaidentity/cli porta login --server https://porta.local:3443 porta org listDocker wrapper (infrastructure commands only):
curl -fsSL https://raw.githubusercontent.com/blendsdk/porta-identity/main/docker/porta.sh \ -o porta && chmod +x portaThen run commands directly:
./porta init ./porta migrate status
porta login --server https://porta.local:3443
The standalone CLI opens your browser for OIDC authentication.
Porta is running at http://localhost:3000. The OIDC discovery endpoint is available at http://localhost:3000/{org-slug}/.well-known/openid-configuration.
| Variable | Default | Description |
|---|---|---|
NODE_ENV | production | Runtime mode |
PORT | 3000 | HTTP server port |
HOST | 0.0.0.0 | HTTP listen address |
DATABASE_URL | — | PostgreSQL connection string (set in compose) |
REDIS_URL | — | Redis connection string (set in compose) |
ISSUER_BASE_URL | — | Required. Public URL of your Porta instance |
COOKIE_KEYS | — | Required. Cookie signing key (≥32 random chars) |
TWO_FACTOR_ENCRYPTION_KEY | — | Required. AES-256-GCM root key (exactly 64 hex characters) |
SIGNING_KEY_ENCRYPTION_KEY | — | Required. Different AES-256-GCM root key (exactly 64 hex characters) |
TRUST_PROXY | true | Set false when Porta is directly exposed without a reverse proxy |
SMTP_HOST | — | SMTP relay hostname |
SMTP_PORT | 587 | SMTP port |
SMTP_USER | — | SMTP username |
SMTP_PASS | — | SMTP password |
SMTP_FROM | — | Sender email address |
LOG_LEVEL | info | Log verbosity (debug, info, warn, error) |
PORTA_AUTO_MIGRATE | false | Keep disabled; apply migrations as a controlled deployment step |
PORTA_WAIT_TIMEOUT | 60 | Seconds to wait for DB/Redis at startup |
# Cookie signing key (random 64-char string)
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
# Two-factor encryption key (64 hex chars)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Signing key encryption key (64 hex chars)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Database password
node -e "console.log(require('crypto').randomBytes(24).toString('base64url'))"
Generate the two encryption root keys separately and keep the results in the deployment environment or a secret manager. The values must be different.
Root keys remain outside PostgreSQL.
porta keys generate adds another active signing key.
It does so without retiring existing active keys.
porta keys rotate retires every active signing key and creates one new active key. After either
successful command, restart every running Porta instance. After restarting, run porta keys list
and verify the committed active signing key.
# Stop all services (preserves data)
docker compose down
# Stop and remove all data (fresh start)
docker compose down -v
Porta's login pages, consent screens, password reset forms, and all emails are fully customizable.
Set logo, colors, and company name without touching any files:
porta org branding <org-id> \
--logo-url "https://cdn.example.com/logo.png" \
--primary-color "#E11D48" \
--company-name "Acme Corp"
For complete UI control, mount your own Handlebars templates:
services:
porta:
image: blendsdk/porta:latest
volumes:
- ./my-templates:/app/templates/default:ro
Copy the default templates as a starting point:
docker cp porta-app:/app/templates/default/. my-templates/
Then edit any file in my-templates/ — layouts, pages, partials, or emails.
See the full Custom UI Tutorial for details.
| 📖 Full Documentation | Guides, API reference, CLI docs |
| 💻 GitHub Repository | Source code and issue tracker |
| 🚀 Quick Start Guide | Detailed setup instructions |
| 📋 Admin API Reference | REST API for admin operations |
| 💻 CLI Reference | Command-line admin tool |
| 🏗️ Architecture | Design and architecture overview |
| 🚢 Deployment Guide | Production deployment guidance |
Content type
Image
Digest
sha256:3cd8f83f0…
Size
72.8 MB
Last updated
4 days ago
docker pull blendsdk/porta