Skip to content

Overview and architecture

This guide rebuilds a personal site and the small platform under it, from an empty folder to push-to-deploy. It is written from a real rebuild, so it includes the parts that went wrong — those are usually the useful parts.

visitor ──HTTPS──▶ Cloudflare ──HTTPS──▶ VPS :443 (only Cloudflare IPs allowed)
(DNS, proxy, │
Full (strict) TLS) ▼
Traefik v3.7 ── certs: Let's Encrypt (DNS-01)
│ │
jonathandobson.com│ │git.fromjon.com
▼ ▼
Next.js 16 site Gitea 28 ──── PostgreSQL 18
(read-only │ registry (private network)
container) │
▲ ▼
pulls :main ────┘ Gitea Runner 5.3 (own Docker daemon)
every 2 min builds + pushes images
you ──SSH──▶ Cloudflare Tunnel ──▶ VPS (sshd listens on 127.0.0.1 only)
Piece Version used Job
Cloudflare — DNS, proxy, TLS to the edge, SSH tunnel, Turnstile, R2 (backups)
VPS Debian 13, Docker 29 Everything below runs here in containers
Traefik + docker-socket-proxy v3.7.17, v0.5.0 HTTPS routing, certificates, rate limits, security headers
The site Next.js 16.4.5 on Node 26 jonathandobson.com and www.
Resend Node SDK 6.37 Sends the contact-form emails
Gitea + PostgreSQL 28.3.0 + 18.6 Git hosting, container registry, Actions
Gitea Runner 5.3.0 (-dind) Runs CI jobs in its own Docker daemon
restic 0.19.1 Encrypted backups to R2 (planned)
  • A VPS with a public IPv4 address and Docker installed.
  • A domain on Cloudflare (this guide uses three: jonathandobson.com, fromjon.com, mrweb.dev) with proxied DNS records pointing at the VPS.
  • SSH to the VPS through a Cloudflare Tunnel, with sshd bound to 127.0.0.1 — nothing listens on port 22 publicly.
  • Accounts you’ll create keys in along the way: Cloudflare (API token, Turnstile, R2) and Resend (a verified sending domain).
  • Node 26 locally (it became the Active LTS line in October 2026).

These came out of the rebuild and are worth adopting before you start:

  1. Look before touching. Inventory the server read-only first. The VPS here looked empty but held the data of a previous setup (Audit).
  2. Leave the old system alone until the new one is live. Back it up, don’t delete it, and retire it by name at the end (Retiring).
  3. Secrets live in files, mode 600 — never in env files, command lines or chat. Generate them on the server where they’re used.
  4. Check claims against the docs for the exact version you run. Several bugs here came from configs written against an older version’s docs.
  5. Prove each step from outside — curl the public URL, read the response headers, check the certificate chain — rather than trusting a green status.
  1. The site — prototype, Next.js 16 app, contact form, tests.
  2. The server — audit, firewall, Traefik, certificates.
  3. Going live — the image, secrets, first deploy.
  4. Gitea — forge, database, Actions runner.
  5. CI/CD — tests and images on every push, pull-based deploys.
  6. Operations — backups, retiring the old system, lessons learned.