Architectuur

Hoe het platform draait — VPS, broncode en database.

Productie

Eén Hetzner VPS host alles. Drie services in Docker Compose, georchestreerd vanuit /opt/. Caddy doet reverse-proxy en regelt automatisch Let's Encrypt-certificaten voor beide domeinen.

Hetzner VPS (178.104.162.125) └── /opt/ ├── docker-compose.yml ← orchestrator ├── Caddyfile ← reverse proxy, auto-TLS ├── .env / heideweken.env ← per-app secrets ├── Dockerfile.dinamotheek ├── Dockerfile.heideweken ├── core-patterns/ ← shared lib (build context) ├── dinamotheek/ ← extracted source (NIET een git clone) ├── heideweken/ ← idem ├── backup.sh ← daily pg_dump → STACK via rclone └── backups/ ← 7 dagen retentie Containers: opt-db-1 Postgres 16 opt-app-1 Dinamotheek opt-heideweken-1 Heideweken opt-caddy-1 reverse proxy

Domeinen

dinamotheek.bramjoosten.nl → opt-app-1 (admin / bron) heideweken.bramjoosten.nl → opt-heideweken-1 (publieke site) code.bramjoosten.nl → Forgejo (eigen Git host)

Source code

Drie aparte repositories, elk met eigen lifecycle. core-patterns is een lokaal-gelinkte npm-package (@bram/core) waardoor beide apps dezelfde entiteit-types, validatie en utilities delen zonder publish-stap.

~/Repositories/ ├── dinamotheek/ Next.js + Postgres admin systeem ├── heideweken/ Next.js + Postgres publieke site ├── core-patterns/ gedeelde TypeScript schema, auth, sanitization └── dinamo-docs/ Next.js deze docs dependencies: dinamotheek/package.json → "@bram/core": "file:../core-patterns" heideweken/package.json → "@bram/core": "file:../core-patterns"

Deploy

De host blijft op zichzelf staan. Push gaat naar Forgejo (code.bramjoosten.nl); op de VPS wordt main uitgepakt in /opt/<app>/ en herbouwt Compose de container. Geen post-receive hook, geen automatische deploy — push ≠ deploy.

# Vanaf laptop git push origin main # Op de VPS (handmatig of via scripts/deploy.sh) cd /opt rm -rf <app> && mkdir <app> git --git-dir=/opt/git/<app>.git archive main | tar -x -C <app>/ docker compose up -d --build <app> # Voor DB-wijzigingen docker exec -i opt-db-1 psql -U dinamotheek -d dinamotheek < <app>/scripts/<migratie>.sql

in migratieDe oude bare repos in /opt/git/*.git worden vervangen door directe deploy vanuit Forgejo. Tot die migratie klaar is, blijven beide paden gangbaar.

Database

Eén Postgres-instantie (container opt-db-1) draagt alle applicatie-data. Logische scheiding via schemas; fysieke isolatie via rol-grants per app. public.entities is de gedeelde bron van waarheid voor plaatsen, kuddes en personen.

postgres 16 (opt-db-1) └── database: dinamotheek ├── schema: public │ ├── entities ← bron: gebieden, kuddes, schaapskooien, │ │ fietsroutes, personen, provincies │ ├── relations ← bron → doel ("kudde graast op gebied") │ └── events ← append-only audit log ├── schema: heideweken │ ├── locations ← curated grouping van public.entities │ ├── location_entities ← join (locatie ↔ entity, met positie) │ ├── pages ← CMS-blocks JSON per slug │ └── site_settings ← campagne-datum, contact, etc. └── schema: auth └── users ← gedeelde user-pool (scrypt + iron-session)

Data-stroom

1
Beheerder voert in via Dinamotheek
2
public.entities
3
Heideweken cureert via heideweken.locations
4
Bezoeker ziet curated set + klikt door naar organisatie

Backup & recovery

/opt/backup.sh draait dagelijks: een pg_dump van het hele cluster, plus tarball van de configs in /opt/, wordt naar een externe STACK-bucket gepusht via rclone. Lokaal wordt 7 dagen retentie aangehouden in /opt/backups/. Pre-migratie snapshots (zoals voor het droppen van tabellen) worden ad-hoc bovenop deze retentie gemaakt.