Quick Start
Get Porta running in 5 minutes with Docker.
Prerequisites
- Docker with Docker Compose v2+
Looking for other setup methods?
This guide uses Docker Hub images for the fastest setup. For cloning the repo or developing from source, see Setup Alternatives.
Step 1: Create a Project Directory
mkdir porta && cd portaStep 2: Generate Required Secrets
Required — Do Not Skip
Porta requires 3 cryptographic secrets to operate securely. These protect session cookies, 2FA secrets, and signing keys. You must generate unique values — do not use the defaults or placeholders.
Run these commands to generate all three secrets:
# 1. Cookie signing key (base64, at least 32 chars)
echo "COOKIE_KEYS=$(openssl rand -base64 32)"
# 2. Two-Factor encryption key (64 hex chars = 32 bytes, AES-256-GCM)
echo "TWO_FACTOR_ENCRYPTION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")"
# 3. Signing key encryption key (64 hex chars = 32 bytes, AES-256-GCM)
echo "SIGNING_KEY_ENCRYPTION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")"Save the output — you'll paste these into your .env file in Step 4.
| Secret | Purpose | Format |
|---|---|---|
COOKIE_KEYS | Signs OIDC session cookies | Base64 string, ≥32 chars |
TWO_FACTOR_ENCRYPTION_KEY | Encrypts TOTP authenticator secrets at rest | 64 hex characters (32 bytes) |
SIGNING_KEY_ENCRYPTION_KEY | Encrypts ES256 signing key private keys at rest | 64 hex characters (32 bytes) |
Step 3: SMTP — Email is Required
Porta requires a working SMTP server
Magic links, user invitations, password resets, and email-based 2FA all send emails. Without SMTP, these features will fail silently.
For local development, we include MailHog in the Docker Compose file below — it catches all outgoing emails and provides a web inbox at http://localhost:8025. No extra setup needed.
For production, configure a real SMTP server:
| Variable | Example | Description |
|---|---|---|
SMTP_HOST | smtp.sendgrid.net | SMTP server hostname |
SMTP_PORT | 587 | SMTP port (587 for STARTTLS, 465 for SSL) |
SMTP_USER | apikey | SMTP username |
SMTP_PASS | SG.xxxxx | SMTP password or API key |
SMTP_FROM | noreply@yourdomain.com | Sender email address |
Popular options: SendGrid, Amazon SES, Postmark, Mailgun, or any SMTP-compatible service.
Step 4: Create docker-compose.yml
Create a file called docker-compose.yml:
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:-porta_secret}@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:-porta_secret}
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
# ── MailHog (dev email testing) ─────────────
mailhog:
image: mailhog/mailhog
container_name: porta-mailhog
ports:
- "8025:8025" # Web UI
- "1025:1025" # SMTP
profiles:
- dev
volumes:
porta_pgdata:
driver: localMailHog for development
Start with docker compose --profile dev up -d to include MailHog. Then open http://localhost:8025 to see all emails Porta sends.
Step 5: Create .env
Create a .env file and paste in your generated secrets from Step 2:
# ── Server ────────────────────────────────────
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
# ── Database ──────────────────────────────────
POSTGRES_PASSWORD=porta_secret
# ── OIDC ──────────────────────────────────────
ISSUER_BASE_URL=http://localhost:3000
# ── Secrets (paste values from Step 2) ────────
COOKIE_KEYS=<paste-your-cookie-key-here>
TWO_FACTOR_ENCRYPTION_KEY=<paste-your-2fa-key-here>
SIGNING_KEY_ENCRYPTION_KEY=<paste-your-signing-key-here>
# ── Email (MailHog for dev, real SMTP for prod)
SMTP_HOST=mailhog
SMTP_PORT=1025
SMTP_USER=
SMTP_PASS=
SMTP_FROM=noreply@porta.local
# ── Logging ───────────────────────────────────
LOG_LEVEL=info
# ── Startup ───────────────────────────────────
PORTA_AUTO_MIGRATE=true
TRUST_PROXY=falseReplace the secret placeholders!
Replace <paste-your-cookie-key-here>, <paste-your-2fa-key-here>, and <paste-your-signing-key-here> with the values you generated in Step 2. Porta will refuse to start with placeholder values in production.
Step 6: Start Services
# For development (with MailHog email testing):
docker compose --profile dev up -d
# For production (without MailHog):
docker compose up -dWait a few seconds for PostgreSQL and Redis to become healthy, then verify:
curl http://localhost:3000/healthYou should see {"status":"ok","database":"ok","redis":"ok"}.
Step 7: Bootstrap the Admin System
docker exec -it porta-app porta initThis interactive command creates:
- The super-admin organization (
porta-admin) - The admin application with 42 RBAC permissions
- A PKCE client for CLI authentication
- A confidential client for the Admin GUI
- Your first admin user (you'll be prompted for email, name, and password)
You can also run it non-interactively:
docker exec porta-app porta init \
--email admin@example.com \
--given-name Admin \
--family-name User \
--password 'YourSecurePassword123!'Then authenticate the CLI:
docker exec -it porta-app porta loginThe CLI prints an authorization URL — open it in your browser, log in, then paste the callback URL back into the terminal.
Step 8: Install the CLI Wrapper
Download the porta wrapper script for a cleaner command-line experience:
curl -fsSL https://raw.githubusercontent.com/blendsdk/porta-identity/main/docker/porta.sh \
-o porta && chmod +x portaThis script forwards commands to the Porta container, with automatic host file detection for provisioning. Now you can run:
./porta init
./porta login
./porta org listWithout the wrapper
All commands also work with docker exec:
docker exec -it porta-app porta initStep 9: Set Up Your Environment with Provisioning
Now that Porta is running, use declarative provisioning to create your organizations, applications, clients, roles, and permissions in one command.
1. Create a setup.yaml file:
version: "1.0"
organizations:
- name: My Company
slug: my-company
applications:
- name: Web Portal
slug: web-portal
clients:
- client_name: Web App
application_type: web
grant_types:
- authorization_code
- refresh_token
redirect_uris:
- http://localhost:8080/callback
response_types:
- code
scope: openid profile email
roles:
- name: Admin
slug: admin
permissions:
- manage-users
- manage-settings
- name: Viewer
slug: viewer
permissions:
- read-data
permissions:
- name: Manage Users
slug: manage-users
- name: Manage Settings
slug: manage-settings
- name: Read Data
slug: read-data2. Preview what will be created:
./porta provision -f setup.yaml --dry-run3. Apply the configuration:
./porta provision -f setup.yamlWithout the wrapper
docker exec porta-app porta provision -f /dev/stdin < setup.yamlMore examples
The repository includes ready-to-use provisioning files at different complexity levels:
examples/provision-simple.yaml— Single org, one app, public + confidential client, basic RBACexamples/provision-multi-org.yaml— Multi-tenant SaaS with two isolated organizationsexamples/provision-enterprise.yaml— Enterprise setup with multiple apps, custom claims, and system configexamples/provision-full.yaml— Complete feature showcase: users with passwords, application modules, branding, 2FA policy, secret config, role/claim assignments
Read the full Provisioning Guide for the complete file format reference.
Step 10: Verify Everything Works
# Check health
curl http://localhost:3000/health
# List organizations
docker exec porta-app porta org list
# Open MailHog to see test emails (if using dev profile)
# http://localhost:8025Open http://localhost:3000/health in your browser to confirm the server, database, and Redis are all connected.
Stopping & Cleanup
# Stop all services
docker compose down
# Stop and delete all data (fresh start)
docker compose down -vTroubleshooting
Container won't start
docker compose logs portaCommon causes:
- Missing or placeholder secrets — check your
.env(see Step 2) - PostgreSQL not ready yet — the entrypoint waits up to 60 seconds
- Port 3000 already in use — change
PORTin your.envfile
Emails not being sent
- Development: Make sure you started with
--profile devfor MailHog, and check http://localhost:8025 - Production: Verify your SMTP settings. Test with
telnet your-smtp-host 587. - Check Porta logs:
docker compose logs porta | grep -i smtp
Login page loads but authentication fails
Most likely missing TRUST_PROXY=true when running behind a TLS-terminating reverse proxy (nginx, Traefik, cloud load balancer). See Environment Variables → Reverse Proxy.
Health check failing
docker compose ps # All services should show "healthy"
docker compose logs postgres # Check databaseNext Steps
- 📖 Architecture Overview — How Porta is designed
- 🔧 Provisioning Guide — Full provisioning file format reference
- 💻 CLI Reference — All CLI commands
- 📋 Admin API — REST API reference
- 🔑 OIDC & Authentication — How OIDC works in Porta
- 🏢 Multi-Tenancy — Organization-scoped tenancy model
- ⚙️ Environment Variables — Complete configuration reference
- 🚢 Deployment Guide — Production deployment guidance
- 🖥️ Setup Alternatives — Clone & Docker or source development setup