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/orNEXTAUTH_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_MODE—local(default) ors3.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/nodeif 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_LEVEL—debug|info|warn|error. Defaults toinfoin 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.