Skip to content

Docker Deployment Guide

Strum VOD provides multiple deployment modes, from a single docker run command to a fully split production architecture. This guide covers every option with architecture diagrams, configuration, and scaling advice.


Table of Contents


Architecture Overview

Strum VOD is composed of 5 logical services that connect to 3 infrastructure backends:

                          ┌─────────────────────────────────────────────┐
                          │              Strum VOD Platform                 │
                          │                                             │
  Browser ───────────────>│  ┌───────────┐         ┌───────────────┐   │
                          │  │ Dashboard │────────>│   API Server  │   │
                          │  │ (React)   │         │   (Fastify)   │   │
                          │  └───────────┘         └───────┬───────┘   │
                          │                                │           │
                          │                          BullMQ job        │
                          │                                │           │
                          │                        ┌───────▼───────┐   │
                          │                        │  Worker (Node)│   │
                          │                        │ bridge + AI   │   │
                          │                        └───────┬───────┘   │
                          │                    Redis Stream │           │
                          │                        ┌───────▼───────┐   │
                          │                        │ Transcoder    │   │
                          │                        │ (Go + FFmpeg) │   │
                          │                        └───────┬───────┘   │
                          └────────────────────────────────┼───────────┘

                    ┌──────────────────────────────────────┼────────────────┐
                    │              Infrastructure                          │
                    │                                                       │
                    │  ┌─────────┐    ┌─────────┐    ┌──────────────────┐  │
                    │  │ Postgres│    │  Redis  │    │  S3 Storage     │  │
                    │  │  (state)│    │ (queue) │    │  (videos, HLS)  │  │
                    │  └─────────┘    └─────────┘    └──────────────────┘  │
                    └──────────────────────────────────────────────────────┘

Data Flow

1. Upload                          2. Transcode                     3. Playback

Client ──POST──> API               API ──job──> Redis                Browser ──GET──> S3
                  │                              │                     (direct HLS streaming,
                  ▼                              ▼                      API not involved)
          S3 (source upload)              Node worker relays
          Postgres (asset record)          to Go transcoder


                                          FFmpeg HLS ladder
                                          (360p–4320p, single pass)


                                          S3 (HLS segments)
                                          Postgres (status → ready)

Key design: Video playback is served directly from S3. The API only handles metadata and coordination. This means S3 absorbs all bandwidth, and the API stays lightweight.

Pipeline split: the Node worker (apps/worker) is now just a bridge (BullMQ ↔ Redis Streams) + AI pipeline; the actual FFmpeg ladder lives in the Go transcoder (apps/transcoder).


Deployment Modes

Mode 1: All-in-One (Simplest)

Best for: Getting started, small teams, personal use, VPS/single-server deployments.

Everything runs in a single container. PostgreSQL and Redis are embedded and managed automatically. You only provide S3 credentials.

┌──────────────────────────────────────────────────────┐
│                  Strum VOD Container                     │
│                  (port 3000)                         │
│                                                      │
│  ┌────────────────────────────────────────────────┐  │
│  │  entrypoint.sh (process orchestrator)          │  │
│  │                                                │  │
│  │  ┌──────────┐  ┌──────────┐  ┌─────────────┐  │  │
│  │  │ Postgres │  │  Redis   │  │  Worker     │  │  │
│  │  │ (auto)   │  │  (auto)  │  │  (Node)     │  │  │
│  │  └──────────┘  └──────────┘  └─────────────┘  │  │
│  │  ┌──────────────────────────────────────────┐  │  │
│  │  │  Transcoder (Go) + API + Dashboard       │  │  │
│  │  └──────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────┘  │
│                                                      │
│  /data (volume)                                      │
│  ├── postgres/  PostgreSQL data files                │
│  └── redis/     Redis persistence                    │
└──────────────────────────────────────────────────────┘


    S3 Storage (external)
bash
docker run -d \
  --name strum-vod \
  -p 3000:3000 \
  -v strum-vod-data:/data \
  -e S3_ENDPOINT=https://s3.amazonaws.com \
  -e S3_REGION=us-east-1 \
  -e S3_BUCKET=my-bucket \
  -e S3_ACCESS_KEY_ID=AKIA... \
  -e S3_SECRET_ACCESS_KEY=... \
  -e S3_PUBLIC_BASE_URL=https://my-bucket.s3.amazonaws.com \
  -e S3_FORCE_PATH_STYLE=false \
  synapsr/strum-vod

What happens:

  • entrypoint.sh initializes PostgreSQL (trust auth, localhost only) and creates the strum_vod database
  • Starts Redis with persistence (save 60 1)
  • Starts the Go transcoder and the Node worker in the background
  • Starts the API (serves the Dashboard SPA) in the foreground
  • Graceful shutdown on SIGTERM/SIGINT stops all processes

Characteristics:

AspectDetail
Imagesynapsr/strum-vod (all-in-one)
Port3000 (API + Dashboard)
Volume/data (PostgreSQL + Redis)
PostgreSQLEmbedded, localhost only
RedisEmbedded, localhost only, persistence enabled
TranscoderGo binary, hardware-adaptive concurrency
WorkerNode bridge + AI pipeline
DashboardServed by API via @fastify/static (same origin)

Mode 2: All-in-One + External Database

Best for: Production single-server, when you want managed PostgreSQL (Neon, RDS, etc.) or managed Redis (Upstash, ElastiCache, etc.).

Same image as Mode 1, but the entrypoint skips embedded services when their URL is provided.

bash
docker run -d \
  --name strum-vod \
  -p 3000:3000 \
  -e DATABASE_URL=postgresql://user:pass@neon-host:5432/neondb?sslmode=require \
  -e REDIS_URL=redis://elasticache-host:6379 \
  -e S3_ENDPOINT=https://s3.amazonaws.com \
  -e S3_REGION=us-east-1 \
  -e S3_BUCKET=my-bucket \
  -e S3_ACCESS_KEY_ID=AKIA... \
  -e S3_SECRET_ACCESS_KEY=... \
  -e S3_PUBLIC_BASE_URL=https://my-bucket.s3.amazonaws.com \
  -e S3_FORCE_PATH_STYLE=false \
  synapsr/strum-vod

You can also mix: use external PostgreSQL with embedded Redis, or vice versa. Only set the env vars for services you want external.

bash
# External PostgreSQL, embedded Redis
-e DATABASE_URL=postgresql://user:pass@neon-host:5432/neondb?sslmode=require
# (omit REDIS_URL → embedded Redis starts automatically)

Mode 3: Docker Compose — Split Services

Best for: Local development, staging, teams who want to inspect each service independently.

Each service runs in its own container. PostgreSQL, Redis, and MinIO run as separate containers.

┌────────────────────────────── Docker Network ──────────────────────────────┐
│                                                                            │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌────────────────────┐   │
│  │ Postgres │    │  Redis   │    │  MinIO   │    │    MinIO Init     │   │
│  │  16      │    │  7       │    │  (S3)    │    │  (create bucket)  │   │
│  │  :5432   │    │  :6379   │    │  :9000   │    │  (one-shot)       │   │
│  └────┬─────┘    └────┬─────┘    └────┬─────┘    └────────────────────┘   │
│       │               │               │                                    │
│       └───────────────┼───────────────┘                                    │
│                       │                                                    │
│              ┌────────▼────────┐                                          │
│              │   API Server   │◄──────── ┌──────────────┐                │
│              │   :3000→13002  │          │  Dashboard   │                │
│              └────────┬───────┘          │  :3001→13003 │                │
│                       │                  └──────────────┘                 │
│                 BullMQ job                                                │
│                       │                                                    │
│              ┌────────▼────────┐                                          │
│              │  Worker (Node)  │                                          │
│              │   no port       │                                          │
│              └────────┬───────┘                                           │
│                       │  Redis Stream                                     │
│              ┌────────▼────────┐                                          │
│              │  Transcoder (Go)│                                          │
│              │   no port       │                                          │
│              └────────────────┘                                           │
│                                                                            │
│  Shared volumes: uploads (API ↔ Worker ↔ Transcoder), postgres-data,      │
│  minio-data                                                               │
└────────────────────────────────────────────────────────────────────────────┘
bash
git clone https://github.com/Synapsr/strum-vod.git && cd strum-vod
cp .env.example .env
docker compose up -d --build
ServiceInternal portExternal portPurpose
postgres543215432Database
redis637916379Job queue
minio9000 / 900119000 / 19001S3 storage / web console
api300013002REST API
workerNode bridge + AI
transcoderGo FFmpeg ladder
dashboard300113003React SPA
glitchtip*800018000Error tracking (profile glitchtip)

Access points:

Start only the infrastructure (Postgres/Redis/MinIO) with pnpm docker:infra, then run the app via pnpm dev for hot reload.


Mode 4: Production — Full Split

Best for: High-volume production, Kubernetes, horizontal scaling, when you need multiple workers.

Each Strum VOD service uses its own dedicated Dockerfile. Infrastructure (PostgreSQL, Redis, S3) is managed externally.

                         Load Balancer / Reverse Proxy

                    ┌───────────────┼───────────────┐
                    │               │               │
                    ▼               ▼               ▼
             ┌───────────┐  ┌───────────┐   ┌─────────────┐
             │ Dashboard │  │ API       │   │ API         │
             │ (CF       │  │ replica 1 │   │ replica 2   │
             │  Workers) │  └─────┬─────┘   └──────┬──────┘
             └───────────┘        │                 │
                                  └────────┬────────┘

                              ┌────────────▼────────────┐
                              │    Redis (managed)      │
                              │  BullMQ + Redis Streams │
                              └────────────┬────────────┘

                          ┌────────────────┼────────────────┐
                          │                │                │
                   ┌──────▼──────┐  ┌──────▼──────┐  ┌─────▼───────┐
                   │  Transcoder1│  │  Transcoder2│  │  Worker 1  │
                   │  (Go)       │  │  (Go)       │  │  (Node)    │
                   └──────┬─────┘  └──────┬─────┘  └──────┬──────┘
                          │               │               │
                          └───────────────┼───────────────┘

                    ┌─────────────────────┼─────────────────────┐
                    │                     │                     │
                    ▼                     ▼                     ▼
             ┌───────────┐       ┌───────────┐     ┌───────────┐
             │ Postgres  │       │  S3 / CDN │     │  Redis    │
             │ (managed) │       │  (video   │     │ (managed) │
             │ (Neon/RDS)│       │  delivery)│     │           │
             └───────────┘       └───────────┘     └───────────┘

API

bash
docker build -f apps/api/Dockerfile -t strum-vod-api .

docker run -d \
  --name strum-vod-api \
  -p 3000:3000 \
  -e DATABASE_URL=postgresql://user:pass@db-host:5432/strum_vod \
  -e REDIS_URL=redis://redis-host:6379 \
  -e S3_ENDPOINT=https://s3.amazonaws.com \
  -e S3_REGION=us-east-1 \
  -e S3_BUCKET=my-bucket \
  -e S3_ACCESS_KEY_ID=AKIA... \
  -e S3_SECRET_ACCESS_KEY=... \
  -e S3_PUBLIC_BASE_URL=https://cdn.example.com \
  -e S3_FORCE_PATH_STYLE=false \
  -e JWT_SECRET=$(openssl rand -hex 32) \
  strum-vod-api

Worker (Node bridge + AI)

bash
docker build -f apps/worker/Dockerfile -t strum-vod-worker .

docker run -d --name strum-vod-worker-1 \
  -e DATABASE_URL=postgresql://user:pass@db-host:5432/strum_vod \
  -e REDIS_URL=redis://redis-host:6379 \
  -e S3_ENDPOINT=https://s3.amazonaws.com \
  -e S3_REGION=us-east-1 \
  -e S3_BUCKET=my-bucket \
  -e S3_ACCESS_KEY_ID=AKIA... \
  -e S3_SECRET_ACCESS_KEY=... \
  strum-vod-worker

Transcoder (Go — scale horizontally)

bash
docker build -f apps/transcoder/Dockerfile -t strum-vod-transcoder .

docker run -d --name strum-vod-transcoder-1 \
  -e DATABASE_URL=postgresql://user:pass@db-host:5432/strum_vod \
  -e REDIS_URL=redis://redis-host:6379 \
  -e S3_ENDPOINT=https://s3.amazonaws.com \
  -e S3_REGION=us-east-1 \
  -e S3_BUCKET=my-bucket \
  -e S3_ACCESS_KEY_ID=AKIA... \
  -e S3_SECRET_ACCESS_KEY=... \
  strum-vod-transcoder

Each transcoder auto-detects its own CPU/RAM and adjusts concurrency. Multiple instances share the same Redis Stream consumer group. See Scaling.

Dashboard

bash
docker build -f apps/dashboard/Dockerfile \
  --build-arg VITE_API_BASE_URL=https://api.example.com \
  -t strum-vod-dashboard .

docker run -d --name strum-vod-dashboard -p 3001:3001 strum-vod-dashboard

Tip: In production, deploy the Dashboard build output (apps/dashboard/dist/) to Cloudflare Workers + Assets via wrangler deploy --config apps/dashboard/wrangler.toml instead of running a Node.js container for it.


Dockerfiles Reference

FileImageContainsPortSize
Dockerfilesynapsr/strum-vodAPI + Worker + Transcoder + Dashboard + PostgreSQL + Redis + FFmpeg3000~1 GB
apps/api/Dockerfilestrum-vod-apiAPI server only3000~200 MB
apps/worker/Dockerfilestrum-vod-workerWorker + FFmpeg + pg_dump~350 MB
apps/transcoder/Dockerfilestrum-vod-transcoderGo transcoder + FFmpeg + yt-dlp~150 MB
apps/dashboard/Dockerfilestrum-vod-dashboardReact SPA + serve3001~150 MB

All Dockerfiles use pnpm (via corepack) and multi-stage builds (build → runtime) for minimal image sizes. Build artifacts and node_modules are pruned.


Environment Variables

Required (all modes)

VariableDescription
DATABASE_URLPostgreSQL connection string (postgresql://user:pass@host:5432/db)
S3_ENDPOINTS3-compatible endpoint URL
S3_REGIONS3 region
S3_BUCKETS3 bucket name
S3_ACCESS_KEY_IDS3 access key
S3_SECRET_ACCESS_KEYS3 secret key
S3_PUBLIC_BASE_URLPublic URL to access S3 objects (for HLS playback)

Required (split mode only)

VariableDescription
REDIS_URLRedis connection string (redis://host:6379)
JWT_SECRETSecret for JWT auth tokens (min 32 chars, openssl rand -hex 32)

Optional

VariableDefaultDescription
PORT3000API/dashboard port
S3_FORCE_PATH_STYLEfalsePath-style S3 URLs (true for MinIO/Backblaze)
S3_PUBLIC_ENDPOINTsame as S3_ENDPOINTPublic S3 endpoint for browser uploads
CORS_ORIGIN*Allowed CORS origins (comma-separated)
DASHBOARD_URLhttp://localhost:3001Base URL for embed player URLs
SHARED_AUTH_SECRETEnables TUS resumable upload (openssl rand -base64 32)

Scaling (auto-detected, override via env)

VariableDefaultDescription
WORKER_CONCURRENCYautoConcurrent transcode jobs per transcoder
FFMPEG_THREADSautoThreads per FFmpeg process
DB_POOL_SIZEautoPostgreSQL connection pool size

The transcoder logs its computed config at startup:

[transcoder] Hardware-adaptive config:
  CPU cores:      8
  Total RAM:      16.0 GB
  Concurrency:    2 job(s)
  FFmpeg threads: 4 per job
  DB pool size:   6

See the Scaling section below for formulas and recommendations.


Scaling

How auto-detection works

At startup, the Go transcoder reads CPU core count and total RAM (cgroup-aware) to compute:

Concurrency   = max(1, min( floor((RAM - 1GB) / 1.5GB), floor(cores / 4) ))
FFmpeg threads = max(1, floor(cores / concurrency))
DB pool        = max(5, concurrency * 2 + 2)
MachineConcurrencyFFmpeg threadsDB pool
2 cores, 4 GB1 job25
4 cores, 8 GB1 job45
8 cores, 16 GB2 jobs46
16 cores, 32 GB4 jobs410
32 cores, 64 GB8 jobs418

Horizontal scaling (multiple transcoders)

Transcoders are stateless. Run multiple instances against the same Redis Stream consumer group to increase throughput:

                    ┌────────────────────┐
                    │   Redis (Streams)  │
                    │   shared queue     │
                    └─────────┬──────────┘

              ┌───────────────┼───────────────┐
              │               │               │
       ┌──────▼──────┐ ┌─────▼───────┐ ┌─────▼───────┐
       │ Transcoder 1│ │ Transcoder 2│ │ Transcoder 3│
       │  8 cores    │ │  4 cores    │ │  16 cores   │
       │  2 jobs     │ │  1 job      │ │  4 jobs     │
       └────────────┘ └─────────────┘ └─────────────┘

              Total throughput: 7 concurrent transcodes

Each transcoder auto-detects its own hardware independently. Heterogeneous machines work fine.

bash
# Scale transcoders in Docker Compose
docker compose up -d --scale transcoder=3

# Or run separate containers
docker run -d --name transcoder-1 -e ... strum-vod-transcoder
docker run -d --name transcoder-2 -e ... strum-vod-transcoder
docker run -d --name transcoder-3 -e ... strum-vod-transcoder

API scaling

The API is stateless (all state lives in Postgres/Redis). Run multiple replicas behind a load balancer:

bash
docker compose up -d --scale api=2

Note: The Dashboard in split mode is a static SPA. It can be served from Cloudflare Workers + Assets without a Node.js runtime.


Volumes & Data Persistence

All-in-One mode

PathContentCritical
/data/postgres/PostgreSQL data filesYes — losing this loses all metadata
/data/redis/Redis RDB/AOF snapshotsLow — only job queue state
bash
docker run -v strum-vod-data:/data ...

Split mode (Docker Compose)

VolumeUsed byContent
postgres-dataPostgreSQLDatabase files
minio-dataMinIOVideo files + HLS output
uploadsAPI + Worker + TranscoderTemporary upload buffer (shared)

The uploads volume is only needed when using the direct upload endpoint (PUT /v1/assets/:id/upload). If you use pre-signed S3 URLs for uploads (POST /v1/assets/:id/upload-url), the volume can be omitted.


Building from Source

bash
git clone https://github.com/Synapsr/strum-vod.git && cd strum-vod

# All-in-one image
docker build -t strum-vod .

# Individual images
docker build -f apps/api/Dockerfile -t strum-vod-api .
docker build -f apps/worker/Dockerfile -t strum-vod-worker .
docker build -f apps/transcoder/Dockerfile -t strum-vod-transcoder .
docker build -f apps/dashboard/Dockerfile \
  --build-arg VITE_API_BASE_URL=https://api.example.com \
  -t strum-vod-dashboard .

Build order: The Dockerfiles handle the build order internally (@strum-vod/db is built first). No manual steps required.


Networking & Ports

All-in-One

PortService
3000API + Dashboard (single port)

PostgreSQL and Redis bind to 127.0.0.1 (localhost only, not exposed).

Docker Compose (default)

External portInternal portService
130023000API
130033001Dashboard
154325432PostgreSQL
163796379Redis
190009000MinIO S3 API
190019001MinIO Console

Internal communication

Dashboard ──HTTP──> API (:3000)
API ──postgres-js──> PostgreSQL (:5432)
API ──ioredis──> Redis (:6379)
API ──BullMQ──> Redis (:6379) ──> Worker (bridge)
Worker ──XADD──> go:transcode:jobs ──> Transcoder (Go)
Transcoder ──AWS SDK──> S3 (:9000)
Transcoder ──XADD──> go:ai:dispatch ──> Worker (AI pipeline)
Browser ──HLS──> S3 (direct, public URLs)

Quick Reference

I want to...Use
Try Strum VOD in 30 secondsMode 1: All-in-One
Run in production on a VPSMode 2: All-in-One + External DB
Develop locallyMode 3: Docker Compose
Scale for high volumeMode 4: Full Split
Add more transcode capacityScaling: multiple transcoders

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