A live radio station, a real-time news dashboard, a multiplayer game platform, and nine other production services (17 in total), sharing one auth system and one deploy pipeline, running on a single self-hosted server. Designed, built, and operated end-to-end by one engineer: architecture, backend, frontend, real-time systems, and infrastructure.
No VC funding, no growth hacking, no dark patterns: just a platform built the way I'd want to use it.
Keep it simple, make deliberate tradeoffs, and ship.
No frameworks, no magic: just Node.js, Docker, and well-chosen tools for each job.
Seventeen independently deployed services, unified by shared auth and a Caddy reverse proxy.
Click any card to expand the implementation details.
{ type: 'light', effect: 'strobe' }; server validates the effect name against an allowlist and broadcasts to all connected clients--lazer-color CSS custom property; direction classes drive animationGET /video-status, which proxies the Owncast status API and returns { online: true/false }Promise.allSettled; any individual failure returns an empty array without crashing the batchwith_watch_monetization_types=flatrate; streaming-only titles with per-item provider fetch (Netflix, Hulu, Disney+, etc.) displayed as inline badges. Bearer token kept server-side onlytop100in2weeks; one call returns all names and concurrent users, replacing a serial N+1 per-game loopfetchedAtIntl.DateTimeFormat; computes the next UTC fire time from the Denver wall clock so the schedule tracks MDT/MST automatically. Each reschedule logs the next fire timeAdding a new admin-only route, the first pass reused the same permission gate built for services that are meant to be role-configurable (grantable to a "family" tier, for example). Review caught that this made the new route one settings-panel checkbox away from being grantable to a non-admin role, not a bug yet, but a soft boundary on something that should be a hard one. It was reworked to check the admin flag directly, with no permission list in the path at all.
setTimeout; clients only render light states. This ensures both players see lights fire at the same instant regardless of network latencybump to the server. The server only broadcasts staged to the other player (own lane is already animated)greenWillBeAt is computed the moment the tree starts (before the random delay runs), so the "how early" time shown on red light is accurate even if the player fires before the first amberdragrace_leaderboard.json on the shared Docker volume. Raw entries stored; four leaderboard sections (1P Normal · 1P Pro · 2P Normal · 2P Pro) computed on read as best time per user per mode+tree combination, top 10 each. Fouls and timeouts never recordedcheckResults checks room.state === 'done' before broadcasting; prevents the 5-second post-green timeout from sending a duplicate result message after a player has already reactedgames/word-sprint.js, trivia-wager.js, drag-race.js, tower-defense.js, tanks.js) exporting a standard interface. server.js is a thin orchestration layer; it never branches on game typegames/index.js, add a panel and a client-side GameModules entry; core server code untouchedlocalStorage; reopening the page automatically rejoins the active gamelocalStoragegamenight_wins.json; top 10 leaderboard shown on the setup page. Session scores shown live in the lobby for every player (even 0 pts)onPlayerLeave hook fires; rounds advance automatically if all remaining players have already submittedfile-type reads the actual magic bytes before the file is accepted; extension and Content-Type header are ignored. Only then does the file move to its permanent UUID-named pathextractYouTubeId(); only the 11-char ID is stored. On the profile page, a "▶ Play" button inserts a YouTube iframe with autoplay=1; autoplay is gated behind a user gesture so browsers allow it/media/, /profile-pic/, and /profile-bg/ routes. Each validates the userId + filename format with a regex AND verifies a matching record exists before serving; no filename guessing possiblebump(key, success); increments call count and either success or failure counteradmin and member protected from deletion. Role changes and per-user overrides both apply immediately, no redeploy neededAll services run in Docker on a MINISFORUM UM690L mini PC (AMD Ryzen 9 6900HX, 16GB RAM). Caddy connects to each Docker network to route public traffic.
Caddy connects to each Docker network, letting it reverse-proxy any service by container name. All twelve web apps share one user data store and one session file store; a single login works across all subdomains via a shared cookie domain. Jellyfin, Immich, and Plex each run on their own isolated Docker network and are restricted to admin and family roles. Media and photos are stored on a NAS over NFS.
A Mac build script packages the full suite; a server deploy script handles everything from image builds to Caddy restarts.
bash scripts/deploy.sh for an interactive menu (1–27), or pass the number directly. Uses ssh -t so server-side sudo prompts work interactively. rsyncs source excluding secrets, node_modules, and runtime data, then runs the server-side deploy script.--restart unless-stopped, connected to both Docker networks so it can reach every service by container name.