Auch verfügbar in: DE Deutsch

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.

  1. init-dirs — Creates the directories under /data.
  2. postgres-init — Creates cluster, user and database on first start, then postgres starts on 127.0.0.1:5432.
  3. migrate — Waits for Postgres and runs prisma migrate deploy.
  4. redis (:6379) and minio (:9000, console :9001, loopback only) — Start after init-dirs.
  5. api (:4000) and worker — Start only once migrate, minio and redis are ready. The buckets are created by API and worker on start, not by the MinIO service.
  6. web (:3001) and nginx (:3000) — Depend only on init-dirs and therefore start early. Nginx answers /health right 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)

  1. User submits a URL in Studio.
  2. API creates a Video record with status=PENDING, source=EXTERNAL and visibility PRIVATE, then enqueues a job on the import queue.
  3. Worker runs yt-dlp to download the source, then transcodes with FFmpeg.
  4. 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