How this site works

The showcase and the private system live on one server that I administer myself: no website hosting, no external control panel, no one else’s database. Below is what it is built from and which forks had to be resolved. The site is its own main exhibit, so the write-up is a real one, with trade-offs rather than a list of technologies.

01 · Layers: from request to disk

  1. VisitorHTTPS

    The Let’s Encrypt certificate is issued and renewed automatically.

  2. Caddyreverse proxy

    The only container facing the outside world. It holds TLS, the security headers and the CSP, and compresses responses.

  3. Next.js 16application

    App Router, server components. One host serves the showcase (apex), another the private OS: a rewrite separates them by domain rather than by a button.

  4. Data access layerzod → repository → service

    The single place where database queries live. Validation sits at the boundary instead of being scattered across handlers.

  5. PostgreSQLdatabase

    The port is not published at all — the database is reachable only inside the docker network.

  6. Backupspg_dump | gzip | gpg

    A single pipeline, with no plaintext on disk. Only the encrypted file ever leaves the machine.

02 · Decisions, and why they went this way

ADR-004

Defence in depth: a redirect is not protection

A proxy that sends a visitor to the login form is a convenience, not a security boundary. The real check is called on the first line of every server action and every data load. A mistake in the proxy rules then costs an inconvenience rather than a leak.

ADR-011

Secrecy by architecture, not by filter

The most personal data lives in separate tables and is never projected into the shared graph. Were it one table with a “private” flag, secrecy would rest on the filter being right — and a single wrong condition would mean a leak. Here there is simply nothing to filter. A dedicated test watches that the public zone never starts importing the private one: if it goes red, the build does not pass.

ADR-010

The knowledge graph is a projection, not a second database

Graph nodes are assembled from data that is already maintained daily, with manual edges on top. A standalone graph would have to be filled by a second round of data entry — and it would inevitably die. The showcase does not show the whole graph but a curated snapshot: the draft is edited separately, publishing is a separate action, and it can be rolled back.

ADR-008

Three data layers — and a deliberate stop

Validation, repository, service. There are deliberately no aggregates, no event sourcing and no CQRS: for a single-user system that is over-engineering. There are exactly as many layers as pay for themselves.

ADR-001

PostgreSQL, even though SQLite would have been simpler

The trade-off is stated plainly: on my own server SQLite is cheaper to operate. Postgres was chosen for concurrent access from several modules at once, and for an easy path to a managed database if the server ever has to change.

ADR-009

The password hash in base64 — because of one character

The raw hash contains “$”, and environment loaders — both in development and in docker-compose — read it as a substitution and quietly corrupt the value: sign-in broke without a single error in the logs. The alternative, escaping it differently in two places, was rejected as a trap. Stored in base64, it reads the same way everywhere.

03 · Stack

Showcase
React Three Fiber, custom shaders, Lenis, offline mesh baking into a binary format
Application
Next.js 16, React 19, TypeScript, Tailwind CSS, Radix UI
Data
PostgreSQL, Prisma, zod
Infrastructure
Docker Compose, Caddy, Linux VPS
Quality
Vitest, ESLint, offline frame checks for the showcase

04 · Sources

The private system’s code is closed — it holds my personal data. Everything else, including course projects and write-ups, is open.