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.