SentinelStream is a real-time Video Management System (VMS) with AI-powered person detection. It uses a modern event-driven architecture, streaming live video to browsers over low-latency WebRTC, publishing detection events via Redis Pub/Sub (Message Queue), and broadcasting updates to browser clients via WebSockets.
The system consists of five dockerized services orchestrated by Docker Compose:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Browser (React) โ
โ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โ
โ โ Live Video โ โ Alert Feed โ โ
โ โโโโโโโโโฒโโโโโโโโ โโโโโโโโโฒโโโโโโโโ โ
โโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโ
WebRTC โ โ WebSocket
(Media Stream)โ โ (Metadata)
โ โ
โโโโโโโโโดโโโโโโโ โโโโโโโโดโโโโโโโโ
โ Worker โ โ Backend โ
โ (Python) โ โ (Bun + Hono) โ
โโโโโโโโโฌโโโโโโโ โโโโโโโโฒโโโโฌโโโโ
โ โ โ
โ Redis MQ โ โ
โผ โ โ
โโโโโโโโโโโโโโโโ โ โ
โ Redis โโโโโโโโโโโโโโโโโ โ
โ (Pub/Sub) โ โ PostgreSQL
โโโโโโโโโโโโโโโโ โ
โผ
โโโโโโโโโโโโโโโโ
โ PostgreSQL โ
โ Database โ
โโโโโโโโโโโโโโโโ
- Frontend (React + Vite + TS): A responsive dashboard featuring grid views for live cameras, real-time alert logs, custom status overlays, and complete CRUD control over RTSP cameras.
- Backend (Bun + Hono + TS): Handles JWT-based authentication, camera configurations, alert history persistence, WebSocket client coordination, and subscribes to the Redis Pub/Sub MQ.
- Worker (FastAPI + OpenCV + YOLOv8 + aiortc): Ingests RTSP streams, executes YOLOv8 object detection, mirror-flips frames, overlays OpenCV green bounding boxes, and streams H.264 video tracks over WebRTC directly to client browsers.
- Redis (Message Queue): Decouples detection events from the HTTP pipeline, ensuring high-throughput ingestion and buffering.
- MediaMTX (RTSP Simulator): Loops a test video file inside Docker to simulate IP security cameras.
This JSON event payload format is preserved identically across the Python worker, Redis Pub/Sub, Postgres database, and the frontend WebSocket updates:
{
"event_id": "0aeb9a21-8afd-4f91-8c21-65c3d37a5bc7",
"camera_id": "b49a71ff-1219-46f1-883e-c38dc621fa03",
"event_type": "person_detected",
"timestamp": "2026-06-27T13:00:00.123Z",
"confidence": 0.95,
"bounding_box": {
"x": 120,
"y": 80,
"width": 60,
"height": 180
},
"frame_number": 4821,
"thumbnail_url": null
}event_id: Unique UUID generated by the worker to enforce backend-side database deduplication.bounding_box: Normalised relative coordinates{x, y, width, height}of the detected person.thumbnail_url: Future extension for saving frame crops to cloud storage (e.g. AWS S3).
- aiortc (Python): Standard H.264 video track packetization ensures sub-second latency compared to HLS/DASH.
- Non-Trickle ICE: Both sides wait for candidate gathering to complete before the SDP handshake, simplifying signaling across isolated network bridges.
- YOLOv8n (Nano): The smallest variant of YOLOv8 is selected for CPU inference, executing in ~100-150ms.
- Frame Skipping: Inference is run every 10th frame (
DETECT_EVERY_N_FRAMES=10) to prevent CPU exhaustion while keeping the input buffer clear. - Python vs. Go: Python was chosen for the worker to leverage the native, optimized
ultralyticspackage andaiortcbinding, avoiding complex CGo configurations. - OpenCV Drawing: Annotations are drawn directly on frames in memory on the worker. This guarantees absolute synchronization and avoids double-border overlays in React.
- When YOLOv8 finds a person, it publishes the event to Redis channel
sentinel:alerts. - The backend subscribes to this channel. If Redis goes offline, the worker transparently falls back to direct HTTP POST alerts.
- Worker Cooldown: The worker maintains an in-memory rate-limiter, ensuring it only broadcasts one alert per camera every
COOLDOWN_SECONDS(default: 15s). - DB-Level Deduplication: The
alertstable has aUNIQUEconstraint on theevent_idcolumn. The backend executesON CONFLICT (event_id) DO NOTHINGduring insertion. Duplicate posts resolve to200 OKwith{ skipped: true }instead of raising exceptions.
Before launching the services, you must configure the environment variables:
- Copy the
.env.examplefile to create a.envfile at the repository root:cp .env.example .env
- Open
.envand verify/configure the database credentials, ports, and internal worker secrets if needed. The default values are tuned to work out of the box with Docker Compose.
This launches all five dockerized services (Postgres, Redis, MediaMTX, Backend, Worker, Frontend) automatically:
- Make sure Docker Desktop is running.
- Build and launch the containers:
(Subsequent runs can omit
docker compose up -d --build
--buildand start instantly withdocker compose up -d). - Open your browser and navigate to:
http://localhost:5173 - Sign up a test account, log in, and view the simulated live feed!
For active development, running frontend, backend, and worker directly on the host enables fast hot-reloads and debugging.
If you don't want to install Postgres and Redis servers locally, spin up just these infrastructure services:
docker compose up -d postgres redis mediamtxIf running the Postgres service locally or outside of its initial container run, initialize the tables by executing the schema SQL script:
# If using the Docker Postgres service:
docker exec -i sentinel_postgres psql -U your_db_user -d your_db_name < backend/src/db/schema.sql
# If using a local Postgres installation:
psql -U your_db_user -d your_db_name -f backend/src/db/schema.sql- Navigate to the backend folder:
cd backend - Install dependencies:
bun install
- Run the backend in development (watch) mode:
The backend API will start on
bun run dev
http://localhost:3000.
- Navigate to the frontend folder:
cd frontend - Install dependencies:
npm install
- Run the Vite development server:
The frontend UI will start on
npm run dev
http://localhost:5173.
- Navigate to the worker folder:
cd worker - Create and activate a Python virtual environment:
python3 -m venv .venv source .venv/bin/activate - Install dependencies:
pip install -r requirements.txt pip install "torch>=2.6.0" "torchvision>=0.21.0"
- Run the FastAPI worker server:
uvicorn main:app --host 0.0.0.0 --port 8001
If you want to stream your own webcam as a live RTSP stream instead of the loop video:
- Stop the worker container (if running via Docker):
docker stop sentinel_worker
- Run the worker locally (following the steps in Local Development Setup above).
- Publish your webcam feed to MediaMTX using
ffmpeg:ffmpeg -f avfoundation -framerate 30 -video_size 640x480 -i "0" \ -pix_fmt yuv420p -vf "format=yuv420p" \ -c:v libx264 -preset ultrafast -tune zerolatency \ -f rtsp rtsp://localhost:8554/webcam
- In the browser UI (Camera Manager), add a camera with the RTSP URL
rtsp://localhost:8554/webcam.
The Hono backend API endpoints are tested using Bun's built-in fast test runner, with mocked database queries and JWT assertions. Run the tests:
cd backend
bun run testProduction-grade deployment configuration files are structured under infra/k8s/.
To deploy the SentinelStream stack onto a running Kubernetes cluster (e.g. Minikube, Docker Desktop K8s, or GKE):
- Create the dedicated
sentinelstreamnamespace:kubectl apply -f infra/k8s/namespace.yaml
- Deploy the database and message broker:
kubectl apply -f infra/k8s/postgres.yaml kubectl apply -f infra/k8s/redis.yaml
- Deploy the MediaMTX simulator and wait for it to be ready:
kubectl apply -f infra/k8s/mediamtx.yaml
- Deploy the backend API and detection worker:
kubectl apply -f infra/k8s/backend.yaml kubectl apply -f infra/k8s/worker.yaml
- Verify that all components are running:
kubectl get pods -n sentinelstream
- TURN Server Setup: Add a CoTURN container for WebRTC streaming outside local area networks.
- GPU Acceleration: Deploy YOLOv8 onto CUDA/TensorRT runtime environments to process 30+ concurrent cameras at full frame rate.
- Cloud Storage: Save event thumbnails to Amazon S3 buckets.
- Horizontal Pod Autoscaling (HPA): Configure K8s HPA to scale worker pods dynamically based on CPU/GPU utilization spikes.