Skip to content

Configuration

All configuration is done through environment variables. Copy .env.example to .env and adjust as needed.

bash
cp .env.example .env

Application

VariableDefaultDescription
PORT3000API server port
CORS_ORIGIN*Allowed origins for CORS. * for all, or comma-separated list
DASHBOARD_URLhttp://localhost:3001Dashboard base URL

Database

VariableDefaultDescription
DATABASE_URLPostgreSQL connection string (Neon-compatible): postgresql://user:pass@host:5432/database?sslmode=require

Neon pooled endpoints: point DATABASE_URL at the direct endpoint (drop -pooler from the host). The API manages its own pool, and pgBouncer's transaction mode can hand out connections with an empty search_path. See DEPLOYMENT-juninho.md for the full gotcha.

Redis

VariableDefaultDescription
REDIS_URLredis://redis:6379Redis connection string for BullMQ + Redis Streams

S3 / R2 Storage

VariableDefaultDescription
S3_ENDPOINTS3-compatible API endpoint
S3_REGIONus-east-1Region (auto for Cloudflare R2)
S3_BUCKETstrum-vodBucket name
S3_ACCESS_KEY_IDAccess key
S3_SECRET_ACCESS_KEYSecret key
S3_FORCE_PATH_STYLEfalseUse path-style URLs. true for MinIO; false for AWS S3 and Cloudflare R2
S3_PUBLIC_ENDPOINTsame as S3_ENDPOINTPublic endpoint used when generating presigned upload URLs
S3_PUBLIC_BASE_URLPublic URL prefix for HLS playback (e.g. https://pub-hash.r2.dev)
S3_BACKUP_BUCKETBucket for daily pg_dump backups (optional)

Cloudflare R2 notes:

  • R2 does not support per-object ACL: public-read or PutBucketCors via the S3 API.
  • Enable public access and configure CORS rules via pnpm r2:cors:set (config in infra/r2/).
  • Set S3_FORCE_PATH_STYLE=false and S3_REGION=auto for R2.

Security

VariableDefaultDescription
JWT_SECRETSecret for signing session JWTs (required for auth). Generate with openssl rand -hex 32
SHARED_AUTH_SECRETBase64-encoded 32-byte key the API uses to sign and verify the resumable TUS upload JWT (both ends live in apps/api now — no second service to share it with). Required to enable the resumable TUS upload mode. Generate with: openssl rand -base64 32

Dashboard Build-Time Variables (Vite)

These are injected at build time by Vite. They must be set before running pnpm run build -w @strum-vod/dashboard.

VariableDefaultDescription
VITE_API_BASE_URLhttp://localhost:13002API base URL
VITE_TUS_SERVER_URLBase URL the TUS client appends /upload/videos to — normally the same as VITE_API_BASE_URL, since TUS is served by the API. When set, enables the "Resumable (TUS)" upload toggle in the dashboard
VITE_PLAYER_BASE_URLPlayer app URL (e.g. https://player.strum-vod.dev). Used to generate embed codes pointing to the player app
VITE_SENTRY_DSNBrowser error reporting DSN (GlitchTip/Sentry-compatible)

Player App Build-Time Variables (Vite)

VariableDefaultDescription
VITE_API_BASE_URLAPI base URL. Set in apps/player/.env or at build time

Registration

VariableDefaultDescription
REGISTRATION_ENABLEDtrueSet to false to disable new account creation
REGISTRATION_ALLOWED_DOMAINSComma-separated allowed email domains (e.g. company.com,partner.org)

AI Processing (optional)

Omit all AI variables to disable AI features entirely. These can also be set (or overridden) from the dashboard's Settings → AI Provider Configuration.

VariableDescription
TRANSCRIPTION_PROVIDERlocal (default) | deepgram | modal
WHISPER_API_URLOpenAI-compatible transcription endpoint
WHISPER_API_KEYAPI key for Whisper service
WHISPER_MODELModel name (e.g. whisper-1)
DEEPGRAM_API_KEYDeepgram API key (when TRANSCRIPTION_PROVIDER=deepgram)
DEEPGRAM_MODELDeepgram model (e.g. nova-2)
LLM_PROVIDERChapter generation: openai, anthropic, groq, or custom
LLM_API_KEYAPI key for LLM service
LLM_MODELModel name (e.g. gpt-4o-mini, llama-3.3-70b-versatile)
LLM_API_URLCustom LLM endpoint (leave empty for provider default)
AI_ENABLEDSet to false to disable AI even if keys are configured

Async (modal) transcription — extra vars

VariableDescription
WHISPER_WEBHOOK_SECRETHMAC secret Modal signs the callback with (X-Signature). Must match the whisper-webhook-secret Modal Secret. Generate with openssl rand -hex 32
API_PUBLIC_URLPublicly reachable base URL of apps/api — Modal calls back to <API_PUBLIC_URL>/v1/ai/whisper-callback from its own cloud (tunnel or deployed API required; localhost won't work). Set on both apps/api and apps/worker

Transcoding Ladder (Go transcoder)

VariableDefaultDescription
RENDITION_CODECh264h264 or hevc — base codec stamped on every rendition
MAX_RENDITION_HEIGHT0Cap the tallest rendition (0 = full 360p–4320p). E.g. 2160 drops the 4320p rung
HEVC_MIN_HEIGHT0Hybrid ladder: renditions at/above this height encode in HEVC, lower rungs keep the base codec (0 = disabled)
FFMPEG_HWACCELautoauto | vaapi | disabled — hardware acceleration for the ladder

Audio Extraction (optional)

VariableDefaultDescription
AUDIO_PLAYBACK_BITRATE_KBPS128AAC bitrate for playback audio (32–320)
AUDIO_PLAYBACK_SAMPLE_RATE48000Sample rate in Hz (8000–96000)
AUDIO_PLAYBACK_CHANNELS21=Mono, 2=Stereo
AUDIO_AI_BITRATE_KBPS64MP3 bitrate for Whisper AI (32–128)
AUDIO_AI_SAMPLE_RATE16000Sample rate in Hz (8000–48000)

Scaling (auto-detected)

The Go transcoder auto-detects CPU cores and RAM (cgroup-aware) at startup. Override only if the auto-detected values are wrong.

VariableUsed byDefaultDescription
WORKER_CONCURRENCYTranscoder (Go)autoConcurrent transcode jobs
FFMPEG_THREADSTranscoder (Go)autoThreads per FFmpeg process
DB_POOL_SIZEAPI, TranscoderautoPostgreSQL connection pool size
AI_WORKER_CONCURRENCYWorker (Node)3ai-process BullMQ queue concurrency

Node ↔ Go Bridge (Redis Streams)

VariableDefaultDescription
TRANSCODE_STREAM_KEYgo:transcode:jobsBullMQ transcode → Go transcoder stream
AI_DISPATCH_STREAM_KEYgo:ai:dispatchGo transcoder → ai-process queue stream
WEBHOOK_DISPATCH_STREAM_KEYgo:webhook:dispatchGo transcoder → webhook-delivery queue stream

Defaults are fine for a single stack; only override if running multiple independent stacks against the same Redis instance.

Fly.io Worker Wake (optional)

VariableDescription
FLY_WORKER_APPFly app name of apps/worker — used to construct the internal /wake URL
WORKER_HTTP_PORTInternal HTTP port (fly.worker.toml, default 8080)
FLY_API_TOKENFly Machines API token — enables /diagnostics machine listing + force-start

Email / OTP (optional)

VariableDefaultDescription
EMAIL_PROVIDERauto-detectresend | smtp | console
EMAIL_FROMnoreply@strum-vod.localFrom address
RESEND_API_KEYResend API key
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_SECURESMTP server config

Billing (optional)

Adding Stripe keys enables tiered billing, usage metering, and plan limits. Without Stripe, all features are available with no usage restrictions.

VariableDescription
STRIPE_SECRET_KEYStripe secret key
STRIPE_WEBHOOK_SECRETStripe webhook secret
STRIPE_PRO_PRICE_ID / STRIPE_BUSINESS_PRICE_IDPlan price IDs

Webhooks (optional)

VariableDefaultDescription
WEBHOOK_URLDefault org webhook target

Error Tracking (optional — GlitchTip via Docker)

VariableDescription
GLITCHTIP_SECRET_KEYGlitchTip Django secret
GLITCHTIP_DB_PASSWORDGlitchTip Postgres password
GLITCHTIP_DOMAINGlitchTip public URL
SENTRY_DSNAPI error reporting DSN
VITE_SENTRY_DSNDashboard build-time DSN

Example Configurations

Docker Compose (local dev — MinIO)

env
DATABASE_URL=postgresql://strum_vod:strum-vodpassword@postgres:5432/strum_vod
REDIS_URL=redis://redis:6379
PORT=3000

S3_ENDPOINT=http://minio:9000
S3_REGION=us-east-1
S3_BUCKET=strum-vod
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_FORCE_PATH_STYLE=true
S3_PUBLIC_ENDPOINT=http://localhost:19000
S3_PUBLIC_BASE_URL=http://localhost:19000/strum-vod

DASHBOARD_URL=http://localhost:13003
CORS_ORIGIN=http://localhost:13003,http://localhost:13002
VITE_API_BASE_URL=http://localhost:13002

JWT_SECRET=dev-secret-change-me

Production — Cloudflare R2 + Neon

env
DATABASE_URL=postgresql://user:pass@ep-xyz.region.aws.neon.tech/neondb?sslmode=require
REDIS_URL=redis://default:pass@redis.upstash.io:6379

S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
S3_REGION=auto
S3_BUCKET=strum-videos
S3_ACCESS_KEY_ID=<R2 key>
S3_SECRET_ACCESS_KEY=<R2 secret>
S3_FORCE_PATH_STYLE=false
S3_PUBLIC_BASE_URL=https://pub-<hash>.r2.dev

DASHBOARD_URL=https://dashboard.strum-vod.dev
CORS_ORIGIN=https://dashboard.strum-vod.dev,https://player.strum-vod.dev
VITE_API_BASE_URL=https://api.strum-vod.fly.dev
VITE_TUS_SERVER_URL=https://api.strum-vod.fly.dev
VITE_PLAYER_BASE_URL=https://player.strum-vod.dev

JWT_SECRET=<openssl rand -hex 32>
SHARED_AUTH_SECRET=<openssl rand -base64 32>

Production — AWS S3 + RDS

env
DATABASE_URL=postgresql://admin:password@mydb.us-east-1.rds.amazonaws.com:5432/strum_vod
REDIS_URL=redis://my-redis.cache.amazonaws.com:6379

S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
S3_REGION=us-east-1
S3_BUCKET=my-strum-vod-bucket
S3_ACCESS_KEY_ID=AKIA...
S3_SECRET_ACCESS_KEY=...
S3_FORCE_PATH_STYLE=false
S3_PUBLIC_BASE_URL=https://my-strum-vod-bucket.s3.us-east-1.amazonaws.com

VITE_API_BASE_URL=https://api.example.com
JWT_SECRET=<openssl rand -hex 32>

Validation

The API and Worker validate all env vars at startup via Zod (apps/api/src/env.ts, apps/worker/src/env.ts). If any required variable is missing or invalid, the process fails to start with a descriptive error.

TUS resumable upload (apps/api/src/routes/tus.ts) is optional: without SHARED_AUTH_SECRET set, the API logs a warning at startup and doesn't register /upload/videos at all (404, not 401) — the dashboard falls back to presigned-URL upload automatically.

STRUM Proprietary License — © 2026 Strum. All rights reserved.