$ uname -a · uptime

Colophon

Everything on djkimlab.com and the 8 subdomain apps runs on one laptop in my apartment, fronted by a Cloudflare Tunnel. No Vercel, no AWS, no Render. This page documents the stack so the homelab work isn't invisible.

The path of one HTTP request

You hit djkimlab.com
Cloudflare DNS resolves to a Cloudflare edge IP (172.64.x.x)
Cloudflare edge holds the TLS session, checks cf-cache for the page
On miss, it forwards through the Cloudflare Tunnel
cloudflared daemon (systemd service, runs as root) accepts on this laptop
Routes to 127.0.0.1:300X based on subdomain
next-server (one of 9 long-lived Node processes) responds
Cloudflare edge caches per route, then back to you
p95 ≈ 140 ms from Toronto last I checked.

The hardware

Host Samsung Galaxy Book4 Pro 360 (dk-950XBE)
OS Ubuntu 26.04 LTS
Kernel 7.0.0-15-generic
RAM 7.1 GB total — usually 4–5 GB live
Disk 233 GB NVMe — ~25% used
Uplink Cloudflare Tunnel (no inbound ports open)
Cost $0/mo server · ~$10/yr domain

Subdomain → port table

SubdomainPortApp
djkimlab.com3000portfolio + wiki (this site)
ops.djkimlab.com3001Investment Operations Suite
radiology.djkimlab.com3002Radiology AI
rl.djkimlab.com3003Reinforcement Learning Lab
agent.djkimlab.com3004Autonomous AI Agent
audio.djkimlab.com3005Audio Intelligence
lamp.djkimlab.com3006Responsive Lamp (gaze + memory)
quant.djkimlab.com3007Quant Trading Platform
chatbot.djkimlab.com3008Consulate Chatbot

Each row is a long-running next start (or FastAPI + Uvicorn for non-Next apps) inside its own keep-alive.sh loop.

How deploys happen

No CI/CD, no webhook, no auto-pull. The deploy is four shell lines I type after merging a PR:

git pull origin main
cd app && npm install && npm run build
kill $(ss -tlnp | grep ":3000 " | grep -oP 'pid=\K\d+')
# keep-alive.sh sees `next start` exit and restarts it
# within ~2 s, now serving the new .next/ build.

Downtime per deploy: about 2 seconds. Cloudflare's long edge cache (s-maxage=31536000 on SSG routes) means most visitors never notice.

The cloudflared service

The tunnel daemon runs as a systemd service. The auth token used to live in the unit file's ExecStart=, which made it visible to any user via /proc/$PID/cmdline. Now it sits in /etc/cloudflared/tunnel.env (mode 600, root), loaded with EnvironmentFile=, so cmdline shows only cloudflared --no-autoupdate tunnel run.

Why self-host instead of Vercel

  • Cost — 9 separate Next apps on Vercel Hobby is over the free tier. On a laptop I already own, the marginal cost is one power outlet.
  • Backend control — most of these apps need a long-lived FastAPI process for ML inference, WebSocket streaming, or scheduled trading cycles. Vercel functions are not the right shape.
  • Practice — running the whole stack myself keeps the infra muscle that the CKA cert was supposed to prove.

This page's own stack

Framework Next.js 16, App Router, fully SSG except /opengraph-image
Style Tailwind v4 + CSS variables for the dark palette
Fonts Geist + Geist Mono, self-hosted, 4 weights × 2 families = 8 files
Wiki Markdown under /wiki/, loaded at build via gray-matter
Render react-markdown + remark-gfm + rehype-slug + rehype-highlight
SEO Per-page generateMetadata, sitemap.ts, robots.ts, dynamic OG image via next/og