Skip to content
RS-W00WebsitesJul 2026 to presentOngoing

This Portfolio

The site you are reading, served from a PC on my desk behind an edge I hardened myself.

Overview

This site is a project in its own right, and it is the first entry on its own board. Everything you are reading is served from a PC on my desk: a Next.js 15 application with a SQLite database, running in Docker behind Cloudflare and an Nginx edge I configured and hardened myself. Nothing here rents a platform. The code, the database, the proxy and the machine are all mine, which means every layer on this page is something I can show you and defend.

The build

The public site is three pages plus a hidden admin. Projects live in SQLite through Drizzle ORM, and the board renders them as flight strips, the paper strips air traffic controllers use: designator, title, status light, tech tags, and a panel that expands in place with a bespoke visual per project. SQLite is a deliberate choice, not a shortcut. I had already lived through self-hosting Supabase for another project, roughly ten containers and 1.5 to 3 GB of RAM idle, and it crashed overnight on Docker Desktop. This site has one writer and no user accounts, so the correct call is one portable database file, zero extra containers, and Drizzle keeping a clean exit door to Postgres if a future version ever needs it.

The admin is not a login system. One long secret in an environment variable grants a session: both sides are hashed and compared in constant time, the cookie is an HMAC-signed httpOnly token with a 30 day expiry, failed attempts hit a lockout, and the whole area is noindexed and linked from nowhere. The request form validates with Zod, checks Cloudflare Turnstile plus a honeypot, rate limits per IP with an in-memory sliding window (one process, so a Map is the right amount of machinery, not Redis), stores the brief in the database and pings my phone through a webhook. A Playwright suite drives a real browser across the routes, runs axe accessibility checks, and fails the build if a banned dash ever appears in visible text.

The edge

The traffic path is layered so any single failure leaves the rest standing. Cloudflare proxies the domain and hides the home IP. The Windows firewall accepts 80 and 443 only from Cloudflare's published ranges. Nginx presents a Cloudflare origin certificate and, with Authenticated Origin Pulls, requires Cloudflare's client certificate in return, so nothing that is not Cloudflare can even complete a TLS handshake. Anything probing the raw IP or sending a random Host header gets the connection closed with no response at all, and the app itself publishes to loopback only. Because residential IPs move, a small DDNS container rechecks every five minutes and repoints the Cloudflare records; its API token can edit DNS for this one zone and nothing else, and it is handed only to that container, so compromising the internet-facing app cannot expose it.

Keeping it alive

The unglamorous problems were the real work. Windows Update reboots the machine in the small hours, and Docker Desktop starts at sign-in rather than at boot, so restart: unless-stopped alone cannot bring the site back. The fix is automatic logon that locks the console immediately, proven by rebooting and watching the site return with nobody at the keyboard. Backups run nightly inside the app container, because the container holds the SQLite file open in WAL mode through the Docker file share, and opening that same file from the Windows side at the same time can corrupt it. A scripted restore drill then boots the production build against a throwaway copy of the newest backup and exits loudly if it cannot serve the projects. And the whole thing stays portable: code in git, state in one data folder, secrets in one env file. Moving to another machine is a copy and a docker compose up, about fifteen minutes, with a Cloudflare Tunnel compose file kept as the fallback for any network where no port can be forwarded.

The hardest problem

Making a home Windows desktop a safe place to run a public production site. The subtle problem was identity: Nginx trusts the CF-Connecting-IP header to identify visitors for the admin lockout and the form rate limit, and until only Cloudflare can reach the origin, anything that connects to Nginx directly could forge that header. Scoping the firewall to Cloudflare's published ranges narrows the window; Authenticated Origin Pulls closes it, because a connection without Cloudflare's client certificate never completes the TLS handshake. The mundane problem was uptime: Docker Desktop starts at sign-in, not at boot, so an unattended Windows Update reboot would leave the site down at a lock screen until someone physically logged in. Automatic logon paired with an immediate console lock, then a test reboot with nobody signed in, settled it. Neither problem is exotic. Both are exactly what self-hosting actually means.

Start a project

Want something built like this?

Everything on this page was designed, built and hosted by one person. Tell me what you need and I reply with scope, cost and timeline, usually within two working days.

This Portfolio · Raafay Siddiqui Projects