Skip to content

Deployment

Local Development (Docker Compose)

The fastest way to run the full stack locally. Spins up the API, worker, transcoder, dashboard, PostgreSQL, Redis, and MinIO in one command.

bash
git clone https://github.com/Synapsr/strum-vod.git
cd strum-vod
cp .env.example .env
docker compose up -d --build
ServiceHost PortDescription
API13002REST API
Dashboard13003Management UI
PostgreSQL15432Database
Redis16379Job queue
MinIO19000S3-compatible storage
MinIO Console19001Storage admin UI

docker:infra starts only the infrastructure services (postgres, redis, minio, minio-init); docker:dev runs infra + the full pnpm dev stack.

Stopping

bash
docker compose down           # stop services, keep data
docker compose down -v        # stop services + delete volumes

Development Without Docker

Requires local PostgreSQL, Redis, FFmpeg, Go, and MinIO (or any S3-compatible store).

Prerequisites

  • Node.js >= 20
  • pnpm >= 9
  • Go 1.23+ (for apps/transcoder)
  • PostgreSQL 16+
  • Redis 7.x
  • FFmpeg + ffprobe (transcoder)
  • yt-dlp (URL imports)

Setup

bash
git clone https://github.com/Synapsr/strum-vod.git
cd strum-vod
pnpm install
cp .env.example .env

Edit .env for local services:

env
DATABASE_URL=postgresql://strum_vod:strum-vodpassword@localhost:5432/strum_vod
REDIS_URL=redis://localhost:6379
S3_ENDPOINT=http://localhost:19000
S3_PUBLIC_ENDPOINT=http://localhost:19000
S3_PUBLIC_BASE_URL=http://localhost:19000/strum-vod
VITE_API_BASE_URL=http://localhost:13002

Start Each Service

bash
pnpm run build -w @strum-vod/db             # must build first

pnpm run dev -w @strum-vod/api              # API
pnpm run dev -w @strum-vod/worker           # Worker (Node: bridge + AI)
pnpm run dev:transcoder                     # Transcoder (Go: ffmpeg ladder)
pnpm run dev -w @strum-vod/dashboard        # Dashboard on :1337
pnpm run dev:player                         # Player app on :1338

Production: Split Deployment

In production, each app runs on the platform that suits it best.

AppPlatformCommand
apps/apiFly.io (or Railway, any VPS)fly deploy --config apps/api/fly.toml
apps/workerFly.iofly deploy --config fly.worker.toml
apps/transcoderFly.io / dedicated serverfly deploy --config fly.transcoder.toml
apps/dashboardCloudflare Workers + Assetswrangler deploy --config apps/dashboard/wrangler.toml
apps/playerCloudflare Pagespnpm deploy:player (wrangler pages)

Resumable (TUS) video upload has no separate deploy target — it's part of apps/api (src/routes/tus.ts), so it ships with the API.

1. Database & Redis

Use a managed PostgreSQL 16+ instance (Neon, Supabase, AWS RDS) and a managed Redis (Upstash, Redis Cloud):

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

Neon: use the direct endpoint (drop -pooler) — see docs/configuration.md and DEPLOYMENT-juninho.md.

2. Storage — Cloudflare R2

Create R2 buckets and enable public access:

bash
wrangler r2 bucket create strum-vod
wrangler r2 bucket create strum-vod-backups

Apply CORS rules from the repo (required for browser-direct presigned uploads):

bash
pnpm r2:cors:set

R2 does not support per-object ACL: public-read or PutBucketCors via the S3 API. Bucket-level CORS is applied with wrangler (see infra/r2/strum-vod-cors.json).

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

3. API, Worker & Transcoder (Fly.io)

bash
# Authenticate
fly auth login

# Create apps (first time)
fly apps create strum-vod-api --machines
fly apps create strum-vod-worker --machines
fly apps create strum-vod-transcoder --machines

# Set secrets on each app (repeat per app)
fly secrets set \
  DATABASE_URL="postgresql://..." \
  REDIS_URL="redis://..." \
  S3_ENDPOINT="https://..." \
  S3_ACCESS_KEY_ID="..." \
  S3_SECRET_ACCESS_KEY="..." \
  S3_PUBLIC_BASE_URL="https://..." \
  JWT_SECRET="$(openssl rand -hex 32)" \
  SHARED_AUTH_SECRET="$(openssl rand -base64 32)" \
  --app strum-vod-api

# Deploy
fly deploy --config apps/api/fly.toml
fly deploy --config fly.worker.toml
fly deploy --config fly.transcoder.toml

Runtime models:

  • API (apps/api/fly.toml) — always-on.
  • Worker (fly.worker.toml) — autostart/autostop. The API wakes it via HTTP /wake (or the Fly Machines API, FLY_API_TOKEN) whenever a transcode job is enqueued.
  • Transcoder (fly.transcoder.toml) — runs continuously (min_machines_running=1); it's woken by Redis Stream activity, not HTTP, so sleep/wake isn't worth it for the low-idle Go binary.

4. Dashboard (Cloudflare Workers + Assets)

bash
# Build with production env vars
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 \
pnpm run build -w @strum-vod/dashboard

# Deploy
wrangler deploy --config apps/dashboard/wrangler.toml

5. Player App (Cloudflare Pages)

bash
# Create apps/player/.env with:
# VITE_API_BASE_URL=https://api.strum-vod.fly.dev

pnpm run deploy:player    # vite build + wrangler pages deploy --project-name=strum-vod-player

6. Docs Site (VitePress, Cloudflare Pages)

The documentation site (docs/, built with VitePress) deploys to Cloudflare Pages automatically via CI — no manual step needed.

One-time setup:

bash
# Create the Pages project (first time only)
wrangler pages project create strum-vod-docs --production-branch main

Then set the custom domain (e.g. docs.strum-vod.dev) in the Cloudflare dashboard → Pages → strum-vod-docs → Custom domains.

Required GitHub Actions secrets:

SecretValue
CLOUDFLARE_API_TOKENAPI token with Account → Cloudflare Pages → Edit permission
CLOUDFLARE_ACCOUNT_IDCloudflare account ID (dashboard → right sidebar)

Trigger: .github/workflows/docs.yml rebuilds and redeploys on every push to main touching docs/** or the root markdown the site imports (README.md, DOCKER.md, CONTRIBUTING.md, SECURITY.md, CHANGELOG.md, CLAUDE.md, package.json, pnpm-lock.yaml). Pull requests get a unique preview URL; workflow_dispatch forces a redeploy.

Manual deploy (local):

bash
pnpm run deploy:docs      # pnpm docs:build + wrangler pages deploy

Once deployed, point the dashboard sidebar's "Documentation" link at it with VITE_DOCS_URL=https://docs.strum-vod.dev at dashboard build time.


Reverse Proxy (nginx / Caddy)

For the all-in-one Docker image or self-hosted VPS deployment:

api.yourdomain.com    →  api (:3000)
dashboard.yourdomain.com  →  (Cloudflare Workers + Assets)
player.yourdomain.com     →  (Cloudflare Pages)

Caddy example

caddyfile
api.yourdomain.com {
  reverse_proxy localhost:3000
}

Scaling the Transcoder

The Go transcoder is stateless. Run multiple instances to process videos in parallel — each picks jobs from the same Redis Stream consumer group (XREADGROUP, with XCLAIM-based recovery of orphaned messages):

bash
# Fly.io
fly scale count 3 --app strum-vod-transcoder

Each instance auto-detects its own CPU/RAM (cgroup-aware) and adjusts its FFmpeg concurrency. The Node worker (bridge + AI) scales the same way.


One-Click Deploy

For the all-in-one Docker image:

PlatformHow to deploy
EasyPanelAdd Docker app → synapsr/strum-vod
DokployImport from Docker Hub
CoolifyOne-click from Docker image
PortainerCreate stack from compose
RailwayDeploy from Docker image

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