I built a simple live streaming platform and run its infrastructure myself. A broadcaster streams from OBS, and viewers watch in the browser or on a phone, with chat and the option to rewind. This page is about the DevOps side: how it runs, how it ships, how it is monitored and how it pages me.
The product stays unnamed here, so there is no link. The poster above shows the whole system, and the screenshots at the end show the multiview page on test data.
1. How it works
- Ingest — OBS streams over RTMP to MediaMTX, which asks the Go API to check the stream key.
- Video — FFmpeg copies each stream into HLS without re-encoding, so viewers can rewind while it is live.
- Delivery — an nginx origin feeds two regional edge caches (nginx, DNS round robin) and Bunny CDN for everyone else. Signed URLs act as the paywall.
- App — Next.js for the web, Go services for the API and chat, PostgreSQL and Redis, behind Cloudflare and an nginx gateway.
- Runtime — the origin’s services run as containers in Docker Compose.
2. Tech stack
| Area | Tools |
|---|---|
| Containers | Docker, Docker Compose |
| Reverse proxy and TLS | nginx, Cloudflare |
| CDN and caching | Bunny CDN, two regional edge caches (nginx) |
| Video | MediaMTX, FFmpeg, HLS |
| Backend | Go, Next.js, PostgreSQL, Redis |
| CI/CD | GitHub Actions, self-hosted runner |
| Monitoring | Prometheus, Grafana, node_exporter, blackbox_exporter |
| Alerting | Alertmanager, Telegram, Better Stack |
| Backups | pg_dump, cron, Bunny Storage |
3. CI/CD with GitHub Actions
Every push to main deploys itself. Tests, linting and type checks run on GitHub’s runners first; then a self-hosted runner on the server builds and restarts what changed.
- Only what changed — a first job works out which services the push touched, and only those are tested, built and restarted.
- Stream-safe builds — images build one at a time, with Go capped to one core, and a normal deploy never restarts the database, Redis or the media server.
- Waits for broadcasts — while anyone is live, the rewind service is not restarted; the next deploy that finds no broadcast picks it up.
- Secrets — passwords and keys live in GitHub secrets, and Compose refuses to start if one is missing.
- No overlap — deploys queue up and never run at the same time.
4. Monitoring
- Prometheus — scrapes node_exporter for CPU, memory, disk and network, and blackbox_exporter, which probes the video through both CDNs.
- Grafana — dashboards on top of those metrics.
- Player metrics — every viewer’s player reports its delay and stalls, so playback quality is charted per CDN instead of guessed from server logs.
5. Alerting
- Alertmanager → Telegram — Prometheus alert rules fire to Alertmanager, which sends them to Telegram.
- Outside check — Better Stack polls one /healthz endpoint that checks the API, PostgreSQL, Redis, chat, mtx-manager, MediaMTX, the DVR and the frontend. It answers 200 or 503, so one monitor covers the whole stack.
- Why both — if the server dies, its own Prometheus dies with it. The outside check still pages.
6. Backups
A cron job runs pg_dump every night, keeps 14 days on the server and copies each dump to Bunny Storage. A near-empty dump fails the job instead of counting as a backup. The offsite copy is not yet verified in production.

