One VPS, two systems, zero magic
This site isn't just a portfolio — it's a small production platform: a Next.js app with an admin CMS and public API, plus a self-hosted relay that fans one OBS stream out to multiple platforms. Here's how it all fits together.
Topology
Five flows that explain everything
F1 A visitor loads a page
Static pages are pre-rendered and served without touching the database. The public REST API adds API-key auth and per-key rate limiting on top.
F2 I control the stream from the admin dashboard
The browser never talks to the relay. A session-guarded server-side proxy forwards only allowlisted endpoints and injects a shared secret the client never sees.
F3 Going live — the hot path
Zero transcoding on the server: the stream is encoded once at home and byte-copied to every platform, so VPS CPU stays near idle while live. If one platform drops, the others keep going.
F4 The LIVE badge you might see in the navbar
The public endpoint exposes only whether I'm live and where to watch — never keys, stats, or session internals.
F5 Shipping code
The portfolio deploys itself on every push to master. The relay is deployed deliberately by hand — streaming infrastructure shouldn't restart just because I fixed a typo on the blog.
What's reachable from the internet
| Service | Exposure | Why |
|---|---|---|
| nginx (HTTPS) | Public | The site — the only public HTTP door |
| RTMP ingest | Firewalled + authenticated | Accepts exactly one stream: mine |
| Next.js app server | Loopback only | Reached exclusively through nginx |
| stream-relay API | Loopback only | Reached exclusively through the auth proxy |
| MediaMTX control API | Loopback only | Polled by the relay for ingest state |
| PostgreSQL (×2 databases) | Loopback only | Only the two apps ever connect |
Design decisions I'd defend
Two databases, one Postgres
- Each app owns its schema and migrations — a relay migration can never touch portfolio tables.
- Either database can be backed up, restored, or dropped without touching the other.
- Same server, same operational cost — isolation for free.
The relay hides behind the portfolio
- One public origin: no CORS, no second TLS certificate, no second attack surface.
- The portfolio already has authentication — the proxy reuses it instead of duplicating it.
- The relay is built SaaS-ready: everything is keyed by account, so multi-tenancy is additive.
Copy, don't transcode
- ffmpeg's tee muxer duplicates the encoded stream instead of re-encoding it.
- Encoding quality is decided once, in OBS, on hardware built for it.
- Adding a platform is one entry in a map — the engine doesn't change.
Defense in layers
- Internal services bind to loopback — unreachable from the internet by construction.
- Every relay request needs a shared secret; stream keys are AES-256-GCM encrypted at rest.
- APIs only ever return masked credentials, and the firewall drops everything not explicitly opened.
Built and operated by one person. Questions welcome.