Architecture
Inhalt
Overview
ITSWEBER Play ships as a single Docker container that exposes one port (3000). An Nginx instance inside the container multiplexes incoming requests and routes them to the appropriate internal service. There is no need for Docker Compose orchestration or a container network — everything runs in one process tree managed by s6-overlay.
Container Topology
┌─────────────────────────────────────────┐
│ itsweber-play:3000 │
│ ┌──────────────────────────────────┐ │
│ │ Nginx :3000 (internal) │ │
│ └──┬──────────────┬────────────────┘ │
│ │ │ │
│ ┌──▼──┐ ┌───▼────┐ │
│ │ Web │ │ API │ │
│ │:3001│ │ :4000 │ │
│ └─────┘ └───┬────┘ │
│ │ │
│ ┌─────┐ ┌──────┐ │ ┌──────┐ │
│ │Redis│ │MinIO │ │ │Pgsql │ │
│ │:6379│ │:9000 │ │ │:5432 │ │
│ └─────┘ └──────┘ │ └──────┘ │
│ ┌───▼────┐ │
│ │ Worker │ │
│ └────────┘ │
└─────────────────────────────────────────┘
All inter-service communication uses 127.0.0.1. The Web frontend is a Next.js 15 App Router application. The API is a Fastify server exposing tRPC procedures and Better Auth endpoints. The Worker is a BullMQ consumer that runs FFmpeg and yt-dlp jobs.
s6-overlay Init Order
s6-overlay starts the services based on their dependencies and restarts any crashed service automatically. There is no strictly linear order.
init-dirs— Creates the directories under/data.postgres-init— Creates cluster, user and database on first start, thenpostgresstarts on127.0.0.1:5432.migrate— Waits for Postgres and runsprisma migrate deploy.redis(:6379) andminio(:9000, console:9001, loopback only) — Start afterinit-dirs.api(:4000) andworker— Start only oncemigrate,minioandredisare ready. The buckets are created by API and worker on start, not by the MinIO service.web(:3001) andnginx(:3000) — Depend only oninit-dirsand therefore start early. Nginx answers/healthright away.
Services
| Service | Internal Port | Purpose |
|---|---|---|
| Nginx | 3000 | Request multiplexer; routes /api/* (including /api/auth/* and /api/trpc/*) to API, /s3/* to MinIO, everything else to Web |
| Web (Next.js) | 3001 | SSR frontend, Studio, Admin panel |
| API (Fastify) | 4000 | tRPC, Better Auth, upload endpoint, theme events via SSE |
| Worker (BullMQ) | — | FFmpeg transcoding, yt-dlp import, thumbnail extraction |
| Postgres 16 | 5432 | Primary database (Prisma ORM) |
| Redis 7 | 6379 | BullMQ job queues, rate-limit counters, pub/sub for live theme updates |
| MinIO | 9000 | S3-compatible object storage for videos, thumbnails, assets |
Data Flows
Upload
Browser → POST /api/upload (single request, body streamed) → API → MinIO (play-raw)
→ BullMQ job "transcode"
Worker → MinIO (pull raw) → ffprobe + FFmpeg → HLS → MinIO (play-videos/)
Studio → polls the status via tRPC every 2 seconds while PENDING/PROCESSING
External Import (yt-dlp)
- User submits a URL in Studio.
- API creates a Video record with
status=PENDING,source=EXTERNALand visibilityPRIVATE, then enqueues a job on theimportqueue. - Worker runs
yt-dlpto download the source, then transcodes with FFmpeg. - Video becomes visible. Default visibility is
private— the user must explicitly publish it.
Auth
Browser → POST /api/auth/sign-in/email → Better Auth → session cookie (HTTP-only, SameSite=Lax)
Browser → GET /api/trpc/* → API reads cookie → validates session → returns data
Theme Update
Admin saves token change → API writes to DB → SSE event broadcast
Browser receives SSE → replaces <style id="theme-vars"> → live update, no reload
Storage Layout
MinIO organizes data into the following buckets:
| Bucket | Contents |
|---|---|
play-raw |
Original video files from upload or import (kept after transcoding, trim reuses them) |
play-videos |
HLS playlists and segments (.m3u8, init.mp4, .m4s) |
play-thumbs |
Auto-generated thumbnail candidates and selected thumbnails |
play-assets |
Logo, channel avatars, banners, captions |
Only play-raw is private. play-videos, play-thumbs and play-assets get an anonymous read policy on start, and the browser loads HLS, thumbnails and logos directly via /s3/. There are no signed URLs in the code.
Monorepo Packages
| Package | Purpose |
|---|---|
apps/web |
Next.js 15 App Router frontend |
apps/api |
Fastify + tRPC + Better Auth backend |
apps/worker |
BullMQ + FFmpeg + yt-dlp video pipeline |
apps/remotion |
Demo video rendering (Remotion) |
packages/db |
Prisma schema and generated client |
packages/theme |
Tokens, presets and CSS generation for theming (the live editor lives in apps/web) |
packages/shared |
Shared TypeScript types and enums |
packages/storage |
MinIO client wrapper |
docker/all-in-one |
Dockerfile, s6-overlay service definitions, Nginx config |
docker/unraid |
Unraid Community Apps XML template |