Hosting Platform Architecture Plan
The big-picture plan for how Hostking Pro works end to end. Read this if you're new to the team, or if you want to understand the "why" behind any specific decision.
One-paragraph summary
Hostking Pro is a multi-tenant hosting platform. Each tenant gets a dedicated Linux user, a systemd-managed process, an Nginx config in front of it, and an nftables egress block. Sites are deployed via a zip upload, a GitHub webhook, or a CLI command. Everything is orchestrated by a single Node.js Next.js app talking to Supabase (data) and WorkOS (auth).
Layers
1. Edge ā Cloudflare
All HTTP traffic terminates at Cloudflare. We use:
- DNS (with proxying)
- DDoS protection
- WAF rules (managed + custom)
- Origin pull certificate (Cloudflare ā hostkingpro.com:443)
- Rate limiting (free tier: 100 req/min per IP)
Cloudflare caches static assets (.css, .js, .png, .woff2) with Cache-Control: public, max-age=86400. API routes are never cached.
2. Front door ā Nginx (one per host)
client ā Cloudflare ā hostkingpro.com:443 (nginx) ā :3002 (Next.js)
Nginx does:
- TLS termination (Let's Encrypt cert, auto-renewed by
certbot) - HTTP/2
- Request body buffering (for upload endpoints)
- WebSocket passthrough (for SSE deploy logs)
3. App ā Next.js on PM2 (one process)
Next.js renders both the marketing site (SSG) and the dashboard (SSR). PM2 manages the process: restart on crash, log rotation, graceful reload. The Next.js server uses Node 20.
API routes live under src/app/api/.../route.ts. Each route is a small, isolated handler. Auth checks happen per-route via the getSession() helper, which reads the WorkOS-issued iron-session cookie.
4. Data ā Supabase (Postgres + Auth-via-WorkOS)
Supabase holds the truth:
organizationsā one per customer orgprofilesā one per user (WorkOS user id is the primary key, which is text)organization_membershipsā many-to-many joinsite_assetsā every site ever deployeddeployment_logsā every deploy attempt, with full stdoutpodsā the host machines that run the tenantsaudit_logā every privileged action (create site, delete site, invite user, etc.)
We use the service role JWT to bypass RLS for background jobs and webhooks. The per-user server client (created via getServerClient()) respects RLS and is used for everything else.
Auth is not Supabase Auth. We use WorkOS AuthKit. Supabase is data only.
5. Hosts ā Pods (Linux machines)
A Pod is a Linux machine (or a container that looks like one). It runs:
systemdwith one unit per tenant (hostking-<system_username>.service)nftableswith one ruleset per tenant (egress block)- An Nginx config in
/etc/nginx/sites-enabled/<system_username>.conf - A tenant home directory at
/home/hk_usr_<system_username>/
The Pod scheduler watches pods table for unhealthy Pods and migrates sites off. The provisioner (a separate bash toolkit) creates the tenant home, the systemd unit, and the nftables ruleset on demand.
6. Tenants ā `/home/hk_usr_<system_username>/`
Each tenant home contains:
/home/hk_usr_b2222222/
āāā app/ # the deployed code (unpacked zip or git clone)
āāā logs/ # tail-able logs (symlink to systemd journal)
āāā env/ # env vars (file, mode 0600)
āāā data/ # persistent storage for the app
āāā backups/ # nightly tarballs (retention: 7 days)
āāā .runtime-state.json # last-known status (written by the app's /healthz)
The systemd unit starts the app on boot, restarts it on crash, and writes its stdout/stderr to the journal.
Data flow: deploy a site
1. user ā POST /api/deploy with zip
2. Next.js uploads zip to /tmp/<uuid>.zip
3. unzip ā /home/hk_usr_<user>/app/
4. spawn systemd unit ā hostking-<user>.service
5. systemd starts the app, runs npm install if package.json changed
6. systemd writes health status to /home/hk_usr_<user>/.runtime-state.json
7. /api/sites/<id>/health polls that file every 30 seconds
8. dashboard sees "active" status, deploy_logs row updated to "succeeded"
Data flow: GitHub webhook
1. user pushes to their repo
2. GitHub sends POST /api/internal/github-webhook
3. Next.js verifies HMAC-SHA256 signature
4. Next.js writes a deploy_logs row (status=queued)
5. Next.js spawns `git pull && restart` via systemd on the assigned Pod
6. deploy_logs row updated as deploy progresses
7. webhook recipient (configured in /dashboard/github) gets a POST with the final result
What could go wrong (and what we do about it)
| Risk | Mitigation |
|---|---|
| A single site misbehaves (CPU hog, OOM) | cgroup v2 limits (memory_limit_mb, cpu_quota_percent). The Pod scheduler evicts if a site exceeds its quota for >5 min. |
| A single Pod goes down | Pod scheduler migrates sites off. RTO for a single Pod: 30 minutes. |
| The whole DB is corrupted | Nightly pg_dump to S3, 7-day retention. RPO: 6 hours. |
| The whole box is gone | Infra-as-code provisioning script (Phase 4 backlog). RTO: 4 hours. |
| Bad deploy takes down prod | Each tenant's app runs in its own cgroup. A bad Node app cannot exhaust box resources. |
Why not Vercel / Render / Fly / etc.?
We considered buying compute from someone else. Decision: no. Reasons:
- Pricing scales linearly with usage ā they make money when our customers use more resources. We want to make money when our customers use fewer resources.
- Cold starts ā every PaaS has them. We can promise sub-second cold starts because we control the runtime.
- Lock-in ā Vercel's pricing API is opaque, their free tier was downgraded last year, their hobby-tier pricing is hidden behind a "Contact Sales" form.
- Boring tech ā systemd + nginx + nftables is a 20-year-old stack. Our team knows it. We won't be surprised by it.
- Multi-cloud ā we can move Pods between any VPS provider (Hetzner, OVH, DigitalOcean, AWS Lightsail) without rewriting the platform.
Roadmap (post-GA)
- Q3 2026: Stripe integration, agency portal
- Q4 2026: Managed databases, object storage
- Q1 2027: White-label offering for hosting resellers
Open questions
- Should we offer cron jobs? (Yes ā but as a separate tier, not bundled.)
- Should we support custom domains out of the box, or punt to a partner? (Custom domains, since Let's Encrypt is free.)
- What about email forwarding? (Phase 3 of the GA roadmap. Not yet.)