Operations

Deployment

Deploy OpenApe IdP and SP to production.

Deployment

OpenApe is built on Nuxt/Nitro and works out of the box on Vercel.

IdP Deployment

  1. Push to GitHub and link the repository in Vercel Dashboard
  2. Set environment variables in Vercel:
NUXT_OPENAPE_IDP_RP_NAME=My Organization
NUXT_OPENAPE_IDP_RP_ID=id.example.com
NUXT_OPENAPE_IDP_RP_ORIGIN=https://id.example.com
NUXT_OPENAPE_IDP_SESSION_SECRET=<random-32+-chars>
NUXT_OPENAPE_IDP_MANAGEMENT_TOKEN=<random-64-chars>
NUXT_OPENAPE_IDP_ADMIN_EMAILS=admin@example.com
  1. Configure persistent storage in nuxt.config.ts:
export default defineNuxtConfig({
  modules: ['@openape/nuxt-auth-idp'],
  nitro: {
    storage: {
      'openape-idp': {
        driver: 's3',
        bucket: 'my-openape-data',
        region: 'eu-central-1',
        accessKeyId: process.env.S3_ACCESS_KEY,
        secretAccessKey: process.env.S3_SECRET_KEY
      },
      'openape-grants': {
        driver: 's3',
        bucket: 'my-openape-data',
        region: 'eu-central-1',
        accessKeyId: process.env.S3_ACCESS_KEY,
        secretAccessKey: process.env.S3_SECRET_KEY
      }
    }
  }
})
The default storage driver is in-memory — data is lost on every deployment. Always configure S3 or another persistent driver for production.
  1. Configure DNS — add a _ddisa TXT record for your domain:
_ddisa.example.com  TXT  "v=ddisa1 idp=https://id.example.com; mode=open"
  1. Deploy:
git push origin main  # Vercel deploys automatically

SP Deployment

  1. Set environment variables:
NUXT_OPENAPE_SP_CLIENT_ID=app.example.com
NUXT_OPENAPE_SP_SESSION_SECRET=<random-32+-chars>
NUXT_OPENAPE_SP_FALLBACK_IDP_URL=https://id.example.com
  1. Configure Nitro for Vercel in nuxt.config.ts:
export default defineNuxtConfig({
  modules: ['@openape/nuxt-auth-sp'],
  nitro: {
    preset: 'vercel',
    externals: {
      inline: ['@openape/nuxt-auth-sp']
    }
  }
})

Docker

IdP Dockerfile

FROM node:22-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-slim
WORKDIR /app
COPY --from=builder /app/.output .output
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

Docker Compose

services:
  idp:
    build: ./idp
    ports:
      - "3000:3000"
    environment:
      NUXT_OPENAPE_IDP_RP_NAME: "My Organization"
      NUXT_OPENAPE_IDP_RP_ID: "id.example.com"
      NUXT_OPENAPE_IDP_RP_ORIGIN: "https://id.example.com"
      NUXT_OPENAPE_IDP_SESSION_SECRET: "${SESSION_SECRET}"
      NUXT_OPENAPE_IDP_MANAGEMENT_TOKEN: "${MANAGEMENT_TOKEN}"
      S3_ACCESS_KEY: "${S3_ACCESS_KEY}"
      S3_SECRET_KEY: "${S3_SECRET_KEY}"
    restart: unless-stopped

DNS Setup

DDISA TXT Record

For IdP discovery, add a TXT record to your domain:

_ddisa.example.com  TXT  "v=ddisa1 idp=https://id.example.com; mode=open"

Fields:

  • v=ddisa1 — protocol version (required)
  • idp=https://... — IdP URL (required)
  • mode=open — enrollment mode: open (anyone can register) or invite (registration URLs only)

Verify DNS

dig _ddisa.example.com TXT +short
# Expected: "v=ddisa1 idp=https://id.example.com; mode=open"
DNS propagation can take up to 48 hours, but most providers propagate within minutes. Use dig or dnschecker.org to verify.

S3 Storage Configuration

OpenApe supports any S3-compatible storage (AWS S3, MinIO, Cloudflare R2, DigitalOcean Spaces):

nitro: {
  storage: {
    'openape-idp': {
      driver: 's3',
      bucket: 'openape-data',
      region: 'eu-central-1',
      endpoint: 'https://s3.eu-central-1.amazonaws.com',  // Optional for AWS
      accessKeyId: process.env.S3_ACCESS_KEY,
      secretAccessKey: process.env.S3_SECRET_KEY
    }
  }
}

For MinIO (self-hosted):

'openape-idp': {
  driver: 's3',
  bucket: 'openape',
  endpoint: 'http://minio:9000',
  accessKeyId: 'minioadmin',
  secretAccessKey: 'minioadmin',
  forcePathStyle: true  // Required for MinIO
}

Reverse Proxy (nginx / Caddy)

If running behind a reverse proxy, ensure WebSocket and HTTPS headers are forwarded:

Caddy

id.example.com {
  reverse_proxy localhost:3000
}

nginx

server {
    server_name id.example.com;
    listen 443 ssl;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Production Checklist

Security

  • Strong sessionSecret (32+ random characters)
  • Strong managementToken (64+ random characters)
  • Management Token stored in secrets manager, not in code
  • HTTPS everywhere (required for Passkeys)
  • DNSSEC enabled for your domain

Storage

  • Persistent storage configured (S3, filesystem — NOT in-memory)
  • Storage backups enabled
  • Separate storage keys for IdP data and grants

DNS

  • _ddisa TXT record published and verified
  • DNSSEC enabled (recommended)

Monitoring

  • Audit logs collected and retained
  • Alerts on failed authentication attempts
  • Alerts on denied grants