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
→
→
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.