Self-hosting

Iris ships as a single Docker image. You bring your own Postgres, Anthropic key, and Deepgram key. Files live on a mounted volume or in an S3-compatible bucket.

Quick start (Railway)

The easiest path: Railway picks up the repo’s railway.toml and Dockerfile automatically. Provision a Postgres add-on, mount a volume at /data, and set the env vars below.

Environment variables

Required

  • DATABASE_URL — Postgres connection string.
  • JWT_SECRET (and/or NEXTAUTH_SECRET) — at least 32 random bytes.
  • ANTHROPIC_API_KEY — Claude vision + summary.
  • DEEPGRAM_API_KEY — audio transcription.
  • NEXT_PUBLIC_APP_URL — public URL of your deployment. Used in share links, OAuth callbacks, OG images. Must be set in production — the app fails loud otherwise.

Storage

  • STORAGE_MODElocal (default) or s3.
  • STORAGE_PATH — for local mode (default /data; the volume mount).
  • For S3: S3_BUCKET, S3_REGION, S3_ENDPOINT (optional, for R2/MinIO), S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY. Server-side encryption is recommended.

Optional

  • GOOGLE_CLIENT_ID/SECRET, GITHUB_CLIENT_ID/SECRET — OAuth.
  • RESEND_API_KEY, RESEND_FROM_EMAIL — transactional email. Without these, emails log to stdout.
  • SENTRY_DSN, SENTRY_TRACES_SAMPLE_RATE — error tracking. Install @sentry/node if you want this wired up.
  • STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_PRO, STRIPE_PRICE_TEAM — billing. Without them, all workspaces stay on free.
  • LOG_LEVELdebug | info | warn | error. Defaults to info in production.

Migrations

The container runs scripts/migrate.mjs on boot before the Next.js server starts. Migrations are idempotent (IF NOT EXISTS patterns), so a redeploy is safe even if nothing changed.

Disk usage

Each recording stores the raw video plus extracted frames (1 jpg per scene-change second, sampled to the plan’s frame cap). Budget roughly 10–30 MB per minute of recording. Set up a cron / janitor if you’re tight on disk.

Reverse proxy

Iris speaks HTTP on port 3000. Front it with whatever you already use (Caddy, nginx, Traefik). Make sure to forward the X-Forwarded-Proto and X-Forwarded-Host headers, and don’t buffer — video streaming uses Range requests.

Health

GET /api/health returns 200 when DB is reachable, 503 otherwise. Point your load balancer here.


Found a gap or a typo? Open an issue or PR on GitHub.