Migratie naar 2-container opzet (app + database)

De Express-app serveert nu zelf de gebouwde Vite-frontend op / met een
SPA-fallback naar index.html; de aparte nginx-container is vervallen.
Bedoeld voor self-hosting op een Raspberry Pi: één image, één origin,
geen interne proxy en geen CORS tussen frontend en API.

Docker
- Nieuwe multi-stage Dockerfile in de root met targets test, development
  en production; frontend/Dockerfile, frontend/nginx-spa.conf en
  backend/Dockerfile verwijderd
- docker-compose.yml: alleen app + postgres, app publiceert APP_PORT
  (standaard 3400) naar containerpoort 3001
- docker-compose.dev.yml: dev-container draait API (node --watch) en de
  Vite dev-server samen via docker/dev-entrypoint.sh, hot reload intact
- .dockerignore, .env.example, .gitignore en .gitattributes toegevoegd

Backend
- express.static + SPA-fallback op STATIC_DIR (default /app/public), met
  immutable caching voor gehashte assets en no-store voor index.html
- Rate limiting alleen nog op /api — statische bestanden liepen anders na
  200 requests tegen de limiet aan
- Helmet CSP toegespitst op wat de frontend laadt (Google Fonts, inline
  styles, base64-avatars); upgrade-insecure-requests uit voor intern http
- Schema-init verplaatst naar initDb(), niet meer bij import van db.js

Tests en CI/CD
- backend/test/app.test.js: 11 tests (node --test) voor API-routes,
  SPA-fallback, cache- en security-headers, zonder database
- test.ps1 / test.sh: backend-tests, productiebuild en smoketest op de
  echte app + postgres stack
- azure-pipelines.yml: Test -> Build (incl. smoketest) -> Publish
  (multi-arch amd64 + arm64, alleen op main/master of een tag)

Scripts en documentatie
- dev.ps1/dev.sh met targets app en postgres, acties logs en test;
  dev.ps1 up wist de dev-database niet langer (alleen dev.ps1 down)
- release.ps1/release.sh bouwen één image voor amd64 + arm64 en draaien
  de testsuite vóór het publiceren
- README.md en BEHEER.md bijgewerkt voor de nieuwe structuur

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jordy Hoogeveen
2026-09-04 11:51:29 +02:00
co-authored by Claude Opus 5
parent 3c67381de9
commit bfec3a25f3
29 changed files with 1168 additions and 428 deletions
+92 -31
View File
@@ -1,28 +1,59 @@
# VibeFinance — Beheer
De app draait in **2 containers**: `app` (Express API + gebouwde frontend) en
`postgres`. Eén image, één origin — geen aparte webserver en geen interne proxy.
---
## Ontwikkeling
```powershell
.\dev.ps1 # build + start (standaard)
.\dev.ps1 build # alleen bouwen
.\dev.ps1 build -Target backend # alleen backend bouwen
.\dev.ps1 build -Target frontend # alleen frontend bouwen
.\dev.ps1 up -Target backend # alleen backend herbouwen + herstarten
.\dev.ps1 up -Target frontend # alleen frontend herbouwen + herstarten
.\dev.ps1 push # dev-images naar registry pushen
.\dev.ps1 push -Target backend # alleen backend pushen
.\dev.ps1 push -Target frontend # alleen frontend pushen
.\dev.ps1 # build + start (standaard)
.\dev.ps1 build # alleen bouwen
.\dev.ps1 up -Target app # alleen de app herbouwen + herstarten
.\dev.ps1 up -Target postgres # alleen de database herstarten
.\dev.ps1 logs # logs van de app volgen
.\dev.ps1 test # testsuite draaien
.\dev.ps1 down # stoppen én volumes wissen
.\dev.ps1 push # dev-image naar registry pushen
```
> Op macOS/Linux: gebruik `./dev.sh` met dezelfde argumenten.
> ⚠️ `.\dev.ps1 up` (beide services) en `.\dev.ps1 down` voeren intern `docker compose down -v` uit — dit wist de development-database. Gebruik voor een herstart zonder dataverlies:
In dev draait één container beide processen (zie `docker/dev-entrypoint.sh`):
| Adres | Wat |
|---|---|
| http://localhost | Vite dev-server met hot reload |
| http://localhost:3001/api/health | de API rechtstreeks |
Vite proxyt `/api` naar `localhost:3001` binnen dezelfde container.
> ⚠️ Alleen `.\dev.ps1 down` wist de development-database (`docker compose down -v`).
> `.\dev.ps1 up` doet een gewone `down` en laat het volume staan.
---
## Testen
```powershell
.\test.ps1 # backend-tests + productiebuild + smoketest
.\test.ps1 -Unit # alleen de backend-tests
.\test.ps1 -NoSmoke # tests + build, zonder de stack te starten
```
Alles loopt in Docker; Node op de host is niet nodig.
| Stap | Wat er gebeurt |
|---|---|
| Backend-tests | `Dockerfile` target `test` → `node --test` in `backend/test/`, zonder database |
| Productiebuild | `Dockerfile` target `production` — faalt de Vite-build, faalt deze stap |
| Smoketest | app + postgres starten en `/api/health` en `/` opvragen |
Losse backend-tests draaien in een lopende dev-container:
```bash
docker compose -f docker-compose.dev.yml down
docker compose -f docker-compose.dev.yml up -d --build
docker compose -f docker-compose.dev.yml exec app npm test
```
---
@@ -31,16 +62,38 @@ docker compose -f docker-compose.dev.yml up -d --build
```powershell
# 1. Versienummer in package.json van "x.x.x-dev" naar "x.x.x"
# 2.
.\release.ps1 # bouw, push naar registry, maak Git-tag aan
.\release.ps1 -DryRun # droogloop — toont alle stappen zonder uit te voeren
.\release.ps1 -NoBuild # sla docker build over, push bestaande images
# 2. Changelog.md bijwerken — die sectie wordt de release-notes
# 3. Token met release-rechten zetten
$env:GITEA_TOKEN = "<token>"
# 3. Versienummer in package.json naar "x.x.(x+1)-dev" voor volgende cyclus
.\release.ps1 # test, bouw (amd64+arm64), push, tag, Gitea-release
.\release.ps1 -DryRun # droogloop — toont alle stappen zonder uit te voeren
.\release.ps1 -NoBuild # sla docker build over, alleen tag + release
.\release.ps1 -SkipTests # sla de testsuite over (niet aanbevolen)
# 4. Versienummer in package.json naar "x.x.(x+1)-dev" voor volgende cyclus
```
Het script bouwt één image voor `linux/amd64` en `linux/arm64`, zodat dezelfde
tag op een Raspberry Pi en op een x86-server werkt.
---
## CI/CD (Azure DevOps)
`azure-pipelines.yml` doet in drie stages hetzelfde als de lokale scripts:
| Stage | Wat |
|---|---|
| `Test` | `docker build --target test` + `docker run` → backend-tests |
| `Build` | productie-image bouwen en smoketesten tegen een postgres-container |
| `Publish` | multi-arch image (amd64 + arm64) pushen naar de registry |
`Publish` loopt alleen op `main`/`master` of een tag. Vul vóór het eerste gebruik
de variabele `dockerRegistryServiceConnection` in met de naam van je Docker
Registry service connection in Azure DevOps.
---
## Docker
```bash
@@ -71,29 +124,37 @@ docker exec -i vibefinance_postgres psql -U vibefinance -d vibefinance < backup-
---
## Architectuur — nginx proxy
## Architectuur — één app-container
De frontend-container (nginx) serveert de React-app en proxyt `/api/`-verzoeken intern door naar de backend-container op poort 3001. Dit is geconfigureerd in `frontend/nginx-spa.conf`:
De Express-app serveert zowel de API als de gebouwde frontend:
```nginx
location /api/ {
proxy_pass http://backend:3001;
}
```
| Verzoek | Wat de app doet |
|---|---|
| `/api/...` | REST API (rate limited) |
| `/assets/...` | statische build uit `STATIC_DIR`, 1 jaar `immutable` gecached |
| al het overige | `index.html` — fallback voor React-routing |
> Ontbreekt dit blok, dan krijgen gebruikers "Kan geen verbinding maken met de server" bij het inloggen — ook al draaien alle containers.
`STATIC_DIR` staat standaard op `/app/public`; daar zet de `Dockerfile` de
Vite-build neer. Bestaat die map niet (bijv. in dev), dan serveert de app
alleen de API en logt dat bij het starten.
> Ontbreekt de build in het image, dan krijg je op `/` een 404 en werkt alleen
> `/api/...`. Controleer dan de `frontend-build` stage in de `Dockerfile`.
---
## Logs en diagnose
```bash
# Live logs (dev)
docker compose -f docker-compose.dev.yml logs -f
# Live logs (dev) — beide processen van de app-container
docker compose -f docker-compose.dev.yml logs -f app
# Live logs (productie)
docker compose logs -f
# Shell in de app-container (dev)
docker compose -f docker-compose.dev.yml exec app sh
# Status containers
docker compose ps
@@ -157,7 +218,8 @@ Rollen:
- [ ] `POSTGRES_PASSWORD` instellen op een sterk wachtwoord
- [ ] `JWT_SECRET` aanpassen (genereer met `openssl rand -hex 64`)
- [ ] `CORS_ORIGIN` instellen op je domeinnaam
- [ ] `FRONTEND_PORT` aanpassen indien poort 3300 al in gebruik is
- [ ] `APP_PORT` aanpassen indien poort 3400 al in gebruik is
- [ ] Testsuite groen (`test.ps1` / `./test.sh`)
- [ ] TLS afhandelen via een externe reverse proxy (bijv. Nginx Proxy Manager, Caddy, Traefik)
---
@@ -165,8 +227,7 @@ Rollen:
## Registry
```
vibehoogie.duckdns.org:3000/vibe/vibefinance-backend
vibehoogie.duckdns.org:3000/vibe/vibefinance-frontend
vibehoogie.duckdns.org/vibe/vibefinance-app (frontend + backend in één image)
Tags:
latest productie — meest recente release
@@ -177,7 +238,7 @@ Tags:
Inloggen op registry:
```bash
docker login vibehoogie.duckdns.org:3000
docker login vibehoogie.duckdns.org
```
---