Skip to content

fix(demo): harden Hetzner deploy — read-only plumbing, Traefik/Docker 29, security-headers; default EN - #521

Merged
haksungjang merged 1 commit into
mainfrom
fix/demo-deploy-hardening
Jul 25, 2026
Merged

fix(demo): harden Hetzner deploy — read-only plumbing, Traefik/Docker 29, security-headers; default EN#521
haksungjang merged 1 commit into
mainfrom
fix/demo-deploy-hardening

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

Why

Bringing up the public read-only demo on a fresh Hetzner CX23 (Ubuntu 24.04 + Docker Engine 29) surfaced blockers that made the documented deploy path fail — the read-only demo path had never been validated end-to-end on a current host. This fixes them.

Changes

  • Read-only lock is now actually enforced. DEMO_READ_ONLY / DEMO_ALLOW_SANDBOX_SCANS lived only in .env (compose-interpolation scope) and were never passed into the backend container — so the backend read them as unset and the public read-only boundary silently stayed off (writes accepted). The demo overlay now plumbs both into backend/worker/beat. Verified: /healthdemo_read_only:true, POST /v1/projects403, POST /auth/login200.
  • Traefik on Docker 29+. Docker 29 raised its minimum negotiable API to 1.44 and rejects Traefik v3.2.1's legacy 1.24 client → the docker provider discovered no routers and served a self-signed default cert (no Let's Encrypt, no routing). Bumped to v3.6.1 (adds Docker API version negotiation).
  • security-headers middleware defined. Backend + frontend routers referenced security-headers@file, but no file provider / dynamic config ever defined it → with a working docker provider both routers were invalid. Now defined via docker-provider labels, referenced as @docker (HSTS, nosniff, frame-deny, referrer-policy).
  • First-time visitors default to English. Dropped navigator from the language-detector order so a fresh visitor (no stored choice) uses the English fallback instead of the browser locale. An explicit pick is still persisted to localStorage.

Verification (live on the demo host)

  • Valid Let's Encrypt cert, SPA loads (/ → 200), API routed (/v1/projects → 401 unauth), security headers present.
  • Read-only boundary: writes 403, reads/login work.
  • Demo dataset seed + daily reset timer exercised successfully.

Notes

  • Ships alongside the already-[Unreleased] demo sandbox scan feature (feat(demo): opt-in public sandbox scans + BomLens SBOM upload (CAX11-friendly) #519); target release v0.19.0.
  • Known follow-up (not in this PR): base docker-compose.yml worker deploy.resources.limits.cpus: "4.0" breaks install.sh's base-only up -d on a <4-CPU box under Compose V2 (V1 ignored deploy:). The demo works via the overlay (caps worker at 2.0); the standalone install path on small boxes needs a separate fix.

… 29, security-headers; default EN

The public read-only demo path was never validated end-to-end on a current
host (Ubuntu 24.04 + Docker Engine 29). Fix the blockers found bringing up the
Hetzner CX23/CAX11 demo:

- Plumb DEMO_READ_ONLY / DEMO_ALLOW_SANDBOX_SCANS into backend/worker/beat via
  the demo overlay. They lived only in .env (compose-interpolation scope) and
  never reached the container, so the backend read them as unset and the
  public read-only lock silently stayed OFF (writes accepted). Verified with
  /health demo_read_only:true and a POST write probe returning 403.
- Bump Traefik v3.2.1 -> v3.6.1. Docker 29 raised its minimum negotiable API
  to 1.44 and rejects Traefik's legacy 1.24 client; the docker provider found
  no routers and served a self-signed default cert. v3.6.1 negotiates the API
  version with the daemon.
- Define the security-headers middleware. Both routers referenced
  security-headers@file, but no file provider / dynamic config ever defined it,
  so a working docker provider left both routers invalid. Define it via
  docker-provider labels and reference it as @docker.
- Default first-time visitors to English: drop 'navigator' from the language
  detector order so a fresh visitor (no stored choice) uses the English
  fallback instead of the browser locale. An explicit pick is still persisted
  to localStorage.
@haksungjang
haksungjang merged commit bd65b22 into main Jul 25, 2026
20 checks passed
@haksungjang
haksungjang deleted the fix/demo-deploy-hardening branch July 25, 2026 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant