ZB Case study REF · ZB-CASE-2026
§ 00 Case study

A career site that is its own case study.

CV Next is a recruiter-facing career site — a gated CV dossier, a public crawlable blog, and an AI concierge — shipped as one container behind Traefik on a ~€10-a-month box. It's also the page you're reading this on. Here is what it's built from and the engineering decisions behind it, so the site itself is the proof of work.

1
container, one port 443
~100
backend tests, JUnit 5
0
tools on the AI model
~€10
per month, self-hosted
Browser React SPA · hash-routed
Traefik TLS termination · :443
cvnext container Spring Boot + React static · /api/**
PostgreSQL dossier · blog · analytics
§ 01

Single-origin deployment

The React bundle is built into Spring's static/ directory and served by the same jar, so the browser hits /api/** same-origin — one container, one port 443, no CORS in production. A multi-stage Dockerfile builds the SPA, then copies it into the Spring image.

Trade-off Couples frontend and backend release cadence, in exchange for radically simpler ops and zero cross-origin surface. For a one-owner site that trade is clearly worth it.

§ 02

LinkedIn OAuth, hand-wired

LinkedIn's OIDC is non-conformant, so the ClientRegistration is built programmatically instead of via spring.security.oauth2.*. Three workarounds are preserved: the nonce is stripped from the authorization request (LinkedIn rejects it), token_type is defaulted to Bearer (LinkedIn omits it), and PKCE is disabled.

Trade-off Hand-wiring loses Spring Boot's auto-configuration, but it's the only way to integrate a provider that breaks the spec — and it lets the app boot with no credentials, so local dev works without secrets.

§ 03

An AI concierge that is a pure text generator

"Ask Zakaria" lets signed-in recruiters ask up to 20 questions about the profile, answered by DeepSeek via Spring AI and streamed back as SSE. Because it answers untrusted, jailbreak-prone input, the model is given no tools: no ToolCallback, no @Tool, no MCP starter on the classpath — so even a perfect jailbreak yields only words. Answers render as escaped React text nodes, never dangerouslySetInnerHTML.

Trade-off No agentic actions — but an untrusted-input LLM with zero reach into the filesystem, shell, database, or network is exactly the point. Security by construction, not by prompt.

§ 04

Owner-gated authorization, email as identity

One privileged user — the owner — is identified by matching the authenticated OAuth email against a configured owner-email. /api/analytics/** is owner-only (403 otherwise), /api/cv/** needs any authenticated user, and the public endpoints stay public. /api/** returns 401, not a login redirect, so the SPA drives the login flow itself.

Trade-off Email-as-identity is simple and needs no user table, but it binds authorization to an external provider's claim — acceptable for a single-owner site; a product would model roles.

§ 05

A crawlable blog on the same jar, separate schema

blog.zakaria.lu is served by the same container: a host-dispatch filter routes the blog host so the SPA keeps the apex. The blog is server-rendered and crawlable (the dossier is gated and isn't); posts live in a dedicated blog Postgres schema. The owner authors in a TipTap editor whose HTML is re-sanitized server-side (OWASP) before it's persisted — raw editor HTML is never stored.

Trade-off One deploy unit instead of a separate service — simpler ops, at the cost of sharing a process and a database between two otherwise-independent surfaces.

§ 06

Agent-built, hardened production infrastructure

CI/CD, the Traefik reverse proxy and TLS, container hardening (non-root uid, no-new- privileges, a read-only Docker socket), key-only SSH, fail2ban, and a swapfile on a Hetzner CX22 with on-box Postgres were all built with agentic coding (Claude Code) — real infrastructure, not a demo.

Trade-off Self-hosting on a single box trades managed-cloud elasticity and SLAs for full control and near-zero cost — a deliberate choice for a personal site, and a way to keep operating muscle sharp.

The full architecture & trade-offs, decision by decision →

§ 07 What this demonstrates

Backend depth, security-first instincts, and end-to-end delivery.

One person taking a real product from a typed config surface and race-safe persistence, through secure-by-design auth and AI, to hardened infrastructure and CI/CD — and keeping it honest enough to publish the trade-offs. Built with the same tools I'd bring to a team.

Java 25 Spring Boot 4 Spring Security Spring AI OAuth2 / OIDC PostgreSQL 17 Flyway React 19 TypeScript Vite Thymeleaf Docker Traefik Hetzner GitHub Actions