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.
The finished system
Section titled “The finished system” 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) |
What you need first
Section titled “What you need first”- 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
sshdbound to127.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).
The working rules that shaped everything
Section titled “The working rules that shaped everything”These came out of the rebuild and are worth adopting before you start:
- Look before touching. Inventory the server read-only first. The VPS here looked empty but held the data of a previous setup (Audit).
- 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).
- Secrets live in files, mode 600 — never in env files, command lines or chat. Generate them on the server where they’re used.
- Check claims against the docs for the exact version you run. Several bugs here came from configs written against an older version’s docs.
- Prove each step from outside —
curlthe public URL, read the response headers, check the certificate chain — rather than trusting a green status.
The order of the guide
Section titled “The order of the guide”- The site — prototype, Next.js 16 app, contact form, tests.
- The server — audit, firewall, Traefik, certificates.
- Going live — the image, secrets, first deploy.
- Gitea — forge, database, Actions runner.
- CI/CD — tests and images on every push, pull-based deploys.
- Operations — backups, retiring the old system, lessons learned.