Hetzner Staging Compose
Date: 2026-05-05
Objective
Create the first Hetzner staging package for the preserved legacy platform without touching blinkin-2-platform.
This is not the final production setup yet. It is the first server-side staging shape that can build and run the already imported legacy systems on a native Linux/amd64 host.
What Was Added
docker-compose.hetzner.ymlContainer map for the first Hetzner staging stack.configs/hetzner/*.env.examplePlaceholder-only environment templates for services, Compose-level credentials, and frontend build arguments.configs/hetzner/reverse-proxy.env.examplePlaceholder-only domain and ACME email template for the reverse proxy.configs/hetzner/backup.env.examplePlaceholder-only backup configuration.configs/hetzner/Caddyfile.exampleExample Caddy routing file for TLS and public domains.configs/hetzner/rsync-filter.rulesSource sync filter that excludes generated folders, caches, virtual environments, Wrangler output, and real env files.scripts/hetzner-staging-probe.mjsRead-only check script for required staging files and Compose/YAML validation.package.jsonscripts:hetzner:planhetzner:sync:planhetzner:sync:checkhetzner:sync:dry-runhetzner:sync:pushhetzner:bootstrap:planhetzner:bootstraphetzner:bootstrap:checkhetzner:config:planhetzner:config:statushetzner:config:init:dryhetzner:config:inithetzner:hosthetzner:host:checkhetzner:checkhetzner:envhetzner:env:checkhetzner:healthhetzner:health:checkhetzner:stack:planhetzner:stack:commandshetzner:stack:confighetzner:stack:build:queryhetzner:stack:buildhetzner:stack:uphetzner:stack:pshetzner:stack:proxy:uphetzner:runbook:planhetzner:runbook:commandshetzner:runbook:checkhetzner:runbook:readinesshetzner:runbook:pythonhetzner:runbook:buildhetzner:runbook:databasehetzner:runbook:stackhetzner:runbook:proxydb:local:checkhetzner:db:planhetzner:db:commandshetzner:db:studio:migratehetzner:db:checkhetzner:backup:planhetzner:backup:checkhetzner:restore:planhetzner:evidence:planhetzner:evidence:checkhetzner:evidence:collect
The .gitignore excludes real configs/hetzner/*.env files, so staging secrets are not committed.
Services In The First Staging Stack
The Compose file currently maps these services:
reverse-proxy: optional Caddy TLS reverse proxy behind theproxyCompose profile.postgres: shared Postgres 16 database host for Studio API and Zweistein.redis: shared queue/cache service.minio: S3-compatible object storage for staging media and files.weaviate: optional AI vector database behind theaiCompose profile.studio-api: legacy Studio API NestJS backend.zweistein-server: legacy Zweistein NestJS backend.query-engine: Zweistein Python Query Engine.ingestion-worker: Zweistein Python ingestion worker.zweistein-admin: Zweistein Admin frontend.picasso-editor: Picasso Studio editor frontend.picasso-houston: Picasso Houston app runtime frontend.picasso-widget: Picasso embeddable widget served as static Nginx assets.zweistein-embedding-widget: Zweistein chat/agent embedding widget served as static Nginx assets.
The first staging decision is to expose both widgets as their own static services. A later production reverse proxy can mount these under stable public paths or domains.
Staging Ports
The first staging ports are intentionally explicit:
| Service | Host Port | Container Port | Purpose |
|---|---|---|---|
| Studio API | 18000 |
3000 |
Studio backend health and API |
| Zweistein Server | 18001 |
3000 |
Admin/AI backend under /ai |
| Query Engine | 5001 |
8000 |
Python AI/query runtime |
| Zweistein Admin | 18082 |
80 |
Admin UI under /ai/ |
| Picasso Widget | 18083 |
80 |
Picasso embeddable widget assets |
| Zweistein Embedding Widget | 18084 |
80 |
Zweistein embeddable chat/agent widget assets |
| Picasso Editor | 18080 |
80 |
Studio editor UI |
| Picasso Houston | 18081 |
3000 |
Published app runtime |
| MinIO API | 9000 |
9000 |
Object storage API |
| MinIO Console | 9001 |
9001 |
Object storage admin console |
Production should later put these behind a reverse proxy and TLS. The first staging goal is to prove containers, health checks, and service connectivity.
Compose-Level Runtime Values
configs/hetzner/frontend-build.env is passed into Docker Compose through --env-file. Despite the historical name, it now contains both frontend build values and the Compose-level infrastructure values needed before containers start:
POSTGRES_PASSWORDMINIO_ROOT_USERMINIO_ROOT_PASSWORD
The strict env gate checks these keys because Postgres and MinIO read them before service-specific env_file values are loaded.
Public Reverse Proxy
The first public edge is Caddy, defined as the optional reverse-proxy service in docker-compose.hetzner.yml.
It is behind the proxy profile so the internal stack can be built and health-checked before opening ports 80 and 443.
Create the proxy runtime files on Hetzner through the runtime config helper:
npm run hetzner:config:init:dry
npm run hetzner:config:init
Then replace every domain placeholder in configs/hetzner/reverse-proxy.env.
The example routes:
| Public Surface | Env Key | Upstream |
|---|---|---|
| Studio editor | STUDIO_DOMAIN |
picasso-editor:80 |
| Published apps | APPS_DOMAIN |
picasso-houston:3000 |
| Studio API | STUDIO_API_DOMAIN |
studio-api:3000 |
| Zweistein Admin | ZWEISTEIN_ADMIN_DOMAIN |
zweistein-admin:80 |
| Zweistein API | ZWEISTEIN_API_DOMAIN |
zweistein-server:3000 |
| Picasso Widget | PICASSO_WIDGET_DOMAIN |
picasso-widget:80 |
| Zweistein Widget | ZWEISTEIN_WIDGET_DOMAIN |
zweistein-embedding-widget:80 |
After the internal health gate is green, start the proxy:
docker compose --env-file configs/hetzner/frontend-build.env -f docker-compose.hetzner.yml --profile proxy up -d reverse-proxy
Safe Hetzner Setup Steps
Run these on the Hetzner/Linux staging host, not on the current low-disk Mac:
npm run hetzner:config:plan
npm run hetzner:config:status
npm run hetzner:config:init:dry
npm run hetzner:config:init
Then replace every placeholder in configs/hetzner/*.env with real staging values.
Important: never commit the real .env files.
Source Sync
Print the safe source sync plan:
npm run hetzner:sync:plan
Run the local safety check:
npm run hetzner:sync:check
Set the server target, then dry-run and push:
export HETZNER_SYNC_TARGET="USER@HOST:/opt/LegacyBlinkin-2-Hetzner/"
npm run hetzner:sync:dry-run
HETZNER_SYNC_CONFIRM=1 npm run hetzner:sync:push
The sync helper uses configs/hetzner/rsync-filter.rules so the server receives source files, docs, scripts, Dockerfiles, and .env.example templates without local node_modules, build outputs, virtual environments, caches, Wrangler output, backups, or real .env files.
Server Bootstrap
The first server bootstrap checklist lives in porting/2026-05-05-hetzner-bootstrap.md.
Useful commands:
npm run hetzner:bootstrap:plan
npm run hetzner:bootstrap
npm run hetzner:bootstrap:check
Run the bootstrap gate before treating a Hetzner host as ready for source sync and image builds.
Validation Commands
Print the server-side plan:
npm run hetzner:plan
Check required staging files and Compose/YAML shape:
npm run hetzner:host
npm run hetzner:check
Run the strict host gate on Hetzner before building:
npm run hetzner:host:check
The host gate checks for native Linux x64/amd64, Node.js 20+, Docker, Docker Compose, available disk headroom, and free staging ports.
Show whether real staging env files exist and whether obvious placeholders remain:
npm run hetzner:config:status
npm run hetzner:env
Run the strict env gate before building on Hetzner:
npm run hetzner:env:check
The strict env gate fails if runtime env files are missing, if obvious placeholders such as replace-with-... or example.com remain, or if required keys are empty. It prints only file names, key names, and line numbers, not secret values.
On the current Mac, hetzner:check only validates file presence and YAML because Docker Compose is not available in the local Docker CLI and disk is too tight for heavy image builds. On Hetzner, after the real env files exist, the same command should run Docker Compose config validation.
Stack Runbook
The guarded server-side stack runbook lives in porting/2026-05-05-hetzner-stack-runbook.md.
The milestone runner lives in porting/2026-05-05-hetzner-milestone-runbook.md.
Useful commands:
npm run hetzner:stack:plan
npm run hetzner:stack:commands
npm run hetzner:runbook:plan
npm run hetzner:runbook:commands
Build Order
Build the Python Query Engine first on native Linux/amd64:
npm run hetzner:stack:build:query
Then build the remaining services:
npm run hetzner:stack:build
Run the database gate before treating application startup as ready:
npm run hetzner:db:plan
npm run hetzner:db:studio:migrate
npm run hetzner:db:check
Start the first staging stack:
npm run hetzner:stack:up
npm run hetzner:stack:ps
Health Checks
After startup, inspect the health endpoints:
npm run hetzner:health
Use the strict health gate when staging should be considered ready:
npm run hetzner:health:check
The first green server milestone is not "the UI opens". It is: all containers build, start, and report health on the staging host.
Backup And Restore
The first backup/restore runbook lives in porting/2026-05-05-hetzner-backup-restore.md.
Useful commands:
npm run hetzner:backup:check
npm run hetzner:backup:plan
npm run hetzner:restore:plan
The backup scope covers Postgres, MinIO, Caddy data/config volumes, and runtime config files. Runtime config backups contain secrets and must stay out of git and in restricted, encrypted storage.
Evidence Collection
The first server-side evidence workflow lives in porting/2026-05-05-hetzner-staging-evidence.md.
Useful commands:
npm run hetzner:evidence:plan
npm run hetzner:evidence:check
npm run hetzner:evidence:collect
Run the collector on Hetzner after each major milestone. It writes Markdown evidence into docs/evidence/ and intentionally avoids env values, heavy build output, rsync push output, migration execution output, stack startup output, logs, database rows, and secret files.
Current Verification Evidence
npm run hetzner:planpassed locally.npm run hetzner:hostreports the current host shape. Locally it correctly reports that this Mac is not the target native Linux/amd64 build host and has too little free disk for heavy image builds.npm run hetzner:checkpassed locally with YAML fallback.npm run hetzner:envreports the expected missing real.envfiles locally without printing secret values.npm run hetzner:healthis available as a post-start staging probe. Locally it reports expected failures because the Hetzner stack is not running on this Mac.picasso-widgetandzweistein-embedding-widgetnow have first static Nginx Docker services indocker-compose.hetzner.yml..dockerignorefiles were added for the new widget build contexts so localnode_modules,dist, and.envfiles are not sent to Docker.- Existing Studio API, Zweistein Server/Admin, Query Engine, and Ingestion Worker Docker contexts now also ignore local generated folders, caches, virtual environments, and real env files.
npm run hetzner:sync:plannow prints the guarded source sync flow.npm run hetzner:sync:checkverifies the rsync filter and refuses to proceed if real local Hetzner env/Caddy files exist.npm run hetzner:sync:pushrefuses to runrsync --deleteunlessHETZNER_SYNC_CONFIRM=1or--yesis present.reverse-proxyis now defined as an optional Caddy service with template-based domain routing and TLS.- Backup/restore helper scripts now print first-run backup and restore rehearsal plans.
- Evidence helper scripts now collect server-side status into Markdown without reading secret values or service logs.
- Bootstrap helper scripts now check whether the Hetzner host has the required base tools before source sync and image builds.
- Runtime config helper scripts now create missing ignored Hetzner config files from templates without overwriting existing files.
configs/hetzner/frontend-build.env.examplenow includes required Compose-level Postgres and MinIO credentials so the stack cannot silently rely on placeholder defaults.- Stack helper scripts now provide guarded server-side commands for Compose config, build, up, ps, and proxy startup.
- The local check reports that real
configs/hetzner/*.envfiles are intentionally missing. docker-compose.hetzner.ymluses real.envfilenames for runtime services, not.env.example.configs/hetzner/*.envis ignored by git.
Known Blockers
- The Query Engine Docker image still needs a successful full build on native Linux/amd64 or Hetzner staging.
- The current Mac is too low on disk for more heavy image builds.
- The current Mac fails the strict host gate because it is
darwin arm64, has very little free disk, and has local MinIO/tunnel ports in use. - Docker Compose config was not fully validated locally because the local Docker CLI does not include the Compose plugin.
- The widget containers still need a successful server-side image build and health check on Hetzner.
- The reverse proxy still needs real DNS, real
configs/hetzner/reverse-proxy.env, realconfigs/hetzner/Caddyfile, and a server-side Caddy config validation. - Backup and restore still need a real Hetzner run and restore rehearsal.
- No real Hetzner evidence file exists yet.
- No real Hetzner bootstrap evidence exists yet.
- End-to-end Studio Editor to Houston App to Zweistein Agent/AI runtime is not verified yet.
Next Slice
The next safe slice is to run the Hetzner staging build on a native Linux/amd64 host and capture exact evidence:
- Query Engine image build result.
- Ingestion Worker image build result.
- Backend health checks.
- Frontend health checks.
- One end-to-end published App flow.