Deployment
Deze pagina beschrijft hoe I See You wordt uitgerold: de backend-API, de twee frontend-apps en de losse documentatiesite. Het uitgangspunt is cloud-agnostisch en gericht op lage startkosten met ruimte om mee te schalen. De architectuur die hier wordt uitgerold staat beschreven bij Backend & API (inclusief realtime en persistentie) en Media & opname.
De applicatie wordt vanaf 0 opgebouwd tegen de documentatie als spec. De deploy van de applicatie staat los van de deploy van de documentatiesite.
Uitgangspunten
Section titled “Uitgangspunten”I See You stelt vier eisen die de deployment-keuzes sturen: lage kosten, schaalbaarheid, anonimiteit en een camera-centrische flow met tijdelijke media. Daaruit volgt:
| Eis | Gevolg voor de deployment |
|---|---|
| Lage startkosten | Een serverless-container-platform met scale-to-zero: bij weinig verkeer draaien er geen (betaalde) instances. |
| Schaalbaarheid | Autoscaling op basis van verkeer; de API is stateless ontworpen, dus instances zijn vrij bij te schakelen. |
| Anonimiteit & tijdelijke media | Media in object-storage met een retentie-proces dat altijd draait; geen media in de database. |
| Geen cloud-lock-in | Alleen gestandaardiseerde bouwstenen (Docker-image, managed PostgreSQL, S3-compatibele opslag, Redis). Platformkeuze blijft een config-, geen herontwerp-kwestie. |
Cloud-agnostisch. Concrete platforms worden in dit document uitsluitend als voorbeeld genoemd, zonder voorkeur. Elk serverless-container-platform met scale-to-zero en autoscaling voldoet; voorbeelden zijn Cloud Run, App Runner of Fargate en Container Apps. De keuze wordt vastgelegd in de Beslissingen (ADR’s).
Runtime-componenten
Section titled “Runtime-componenten”De productie-omgeving bestaat uit een handvol losse, vervangbare bouwstenen. De API is het enige zelf-gebouwde proces; de rest is managed of een standaard image.
| Component | Invulling | Schaalt mee |
|---|---|---|
| API | De ISeeYou.Api-Docker-image (Minimal API + SignalR) op een serverless-container-platform. | Ja — scale-to-zero + autoscaling. |
| Frontend-apps | Twee statische React-bundels (beheerportaal + gebruikersportaal), zie Frontend. | Statisch (CDN/object-storage). |
| Database | Managed PostgreSQL (Npgsql), zie Backend & API — Persistentie. | Verticaal/managed. |
| Object-storage | S3-compatibele opslag voor media-bytes via IMediaStore. | Vrijwel onbeperkt. |
| Redis (optioneel) | SignalR-backplane zodra er meer dan één API-instance draait, zie Backend & API — Realtime. | Pas nodig bij scale-out. |
flowchart LR
Browser["Browser (mobiel-kader)"] -->|HTTPS REST| API["API-instances (Docker, autoscaling)"]
Browser -->|"WebSocket (SignalR)"| API
Beheer["Beheerportaal (statische bundel)"] -.-> Browser
Gebruiker["Gebruikersportaal (statische bundel)"] -.-> Browser
API -->|"EF Core / Npgsql"| PG[("Managed PostgreSQL")]
API -->|"IMediaStore (S3 SDK)"| OS[("S3-compatibele object-storage")]
API <-->|"backplane (bij meerdere instances)"| Redis[("Redis (optioneel)")]
Eén API, twee portalen. De twee frontends draaien tegen dezelfde deployable API; de scheiding zit in het autorisatiebeleid (
IsBeheerdervs. gebruiker/anoniem), niet in twee aparte backends. De backend is single-tenant.
Waarom scale-to-zero de stateless API vereist
Section titled “Waarom scale-to-zero de stateless API vereist”Scale-to-zero betekent dat instances verdwijnen bij stilte en bij pieken bij komen. Dat kan alleen omdat de API geen lokale state vasthoudt: sessies staan in de database, media in object-storage, en de enige in-memory state (SignalR-verbindingen) wordt bij meerdere instances over een Redis-backplane gedeeld. Geen enkele socket-state blokkeert dus de scale-out. Tot één instance is Redis niet nodig; het bijschakelen ervan is een config-wijziging, geen herontwerp.
Build → image → deploy
Section titled “Build → image → deploy”De API wordt als één Docker-image gebouwd (multi-stage: dotnet publish → runtime-image) en is daarmee identiek lokaal, in CI en in productie. Het draaiende platform haalt het image uit een container-registry.
flowchart LR
subgraph CI["CI/CD-pijplijn"]
Src["Broncode (app/)"] --> BT["Build + test (xUnit, Testcontainers)"]
BT --> Img["Docker build (ISeeYou.Api)"]
Img --> Push["Push naar container-registry"]
end
Push --> Mig["EF Core-migraties (dotnet ef) als deploy-stap"]
Mig --> Deploy["Deploy image naar serverless-container-platform"]
Deploy --> Run["Draaiende API-instances (scale-to-zero + autoscaling)"]
Run --> PG[("Managed PostgreSQL")]
Run --> OS[("S3-compatibele object-storage")]
CI/CD in het kort
Section titled “CI/CD in het kort”- Build + test — compileren en de testpiramide draaien: xUnit voor domein/applicatie, en Testcontainers-PostgreSQL voor integratie-, endpoint- en mapping-tests (een echte Postgres in een container, geen mocks) — zie de testkeuzes in Repo-indeling & tech stack.
- Docker build — de
ISeeYou.Api-image bouwen (multi-stage). - Image push — de image naar de container-registry pushen, getagd op commit/versie.
- EF-migraties als deploy-stap — vóór het activeren van de nieuwe image de databaseschema-migraties draaien (
dotnet ef database update, of een gebundeld migratie-artefact), inclusief deKeuzelijst-seed. Dit is een aparte, bewuste stap, niet iets dat de app bij opstart impliciet doet. - Deploy — de nieuwe image uitrollen naar het serverless-container-platform; oud verkeer schuift over naar de nieuwe revisie.
De stappen zijn platform-neutraal: ze bestaan uit standaard
dotnet- endocker-commando’s plus een registry-push en een platform-deploy. Daardoor is dezelfde pijplijn bruikbaar tegen elk in de inleiding genoemde voorbeeld-platform. De gebouwde CI/CD (.github/workflows/ci.yml) dekt zowel de verificatie-kant (docs-build, app-lint/typecheck/unit/build via npm workspaces,dotnet test) als de deploy: bij een push naarmainbouwt en rolt de pijplijn automatisch uit naar Azure — alleen de onderdelen waarvan bestanden wijzigden (app/backend/**→ API,app/frontend|beheer|shared/**→ portalen,docs/**→ documentatiesite).
Waarom een self-hosted runner (ADR-033)
Section titled “Waarom een self-hosted runner (ADR-033)”De GitHub-hosted runners van deze organisatie zijn niet beschikbaar (uitgeputte/uitgeschakelde runner-minuten), dus draait de pijplijn op een self-hosted Windows-runner op een ontwikkelaarslaptop (actions/runner, geregistreerd op de repo). Dat brengt een concreet voordeel met zich mee: de laptop heeft al een ingelogde az-CLI-sessie, dus de deploy-jobs kunnen ACR-, Web App- en Static Web Apps-credentials live opvragen (az acr credential show, az staticwebapp secrets list) in plaats van ze als GitHub-secrets te bewaren. Kanttekening: de runner draait alleen zolang het run.cmd-proces (of de service) actief is op die laptop — er is geen scale-to-zero of hoge beschikbaarheid voor de pijplijn zelf. Zie Beslissingen (ADR’s).
Configuratie en geheimen
Section titled “Configuratie en geheimen”De API is hetzelfde image in elke omgeving; verschillen zitten in omgevingsvariabelen/secrets, niet in de build. De belangrijkste:
| Categorie | Voorbeelden |
|---|---|
| Database | PostgreSQL-connectiestring (Npgsql). |
| Object-storage | S3-endpoint, bucket, sleutel/secret voor IMediaStore. |
| Realtime | Redis-connectiestring (alleen bij meerdere instances). |
| Observability | APPLICATIONINSIGHTS_CONNECTION_STRING (leeg → alleen console-logging), zie ADR-032. |
| Beveiliging | Argon2id-parameters, CORS-allowlist, HTTPS/HSTS-instellingen. |
Secrets komen uit de secret-store van het gekozen platform en staan nooit in de repo of de image. De beveiligingsmaatregelen (rate limiting, CORS-allowlist, HTTPS/HSTS, krappe CSP, body-limieten) zijn deels code, deels deploy-configuratie.
Object-storage: lokaal en in productie dezelfde interface
Section titled “Object-storage: lokaal en in productie dezelfde interface”IMediaStore abstraheert de opslag van media-bytes. Er is één S3-compatibele implementatie (AWS SDK for .NET) die tegen elke S3-compatibele opslag werkt — AWS S3, GCS, MinIO en dergelijke. Lokaal draait dezelfde implementatie tegen MinIO; in productie tegen de managed S3-compatibele opslag van het platform. Een opslag die alleen Azure Blob spreekt, kan via de S3-laag of via een dunne AzureBlobMediaStore met dezelfde interface — wisselen is een DI-/config-keuze, geen herontwerp. De database bewaart alleen de key + metadata, nooit de bytes.
Lokaal ontwikkelen
Section titled “Lokaal ontwikkelen”Lokaal draait de hele stack via Docker Compose, zodat de ontwikkelomgeving zo dicht mogelijk bij productie ligt: dezelfde Postgres, dezelfde (S3-compatibele) object-storage, dezelfde API-image.
| Service | Image/rol | Productie-tegenhanger |
|---|---|---|
postgres | PostgreSQL | Managed PostgreSQL |
minio | S3-compatibele object-storage | Managed S3-compatibele opslag |
api | ISeeYou.Api (Docker) | API op serverless-container-platform |
frontend | Vite-dev-server / statische bundels | CDN/object-storage |
cd appdocker compose up # postgres + minio + api + frontendMigraties en de Keuzelijst-seed draaien lokaal met dotnet ef tegen de Compose-Postgres. Redis is lokaal niet nodig (één API-instance); het hoort pas bij scale-out.
.NET Aspire (optioneel). Als .NET-georiënteerde orchestratie kan .NET Aspire de lokale samenstelling (API, Postgres, MinIO, frontend) beschrijven en starten in plaats van of naast Docker Compose. Het is een dev-gemak, geen vereiste en geen productie-afhankelijkheid.
Frontend-deploy
Section titled “Frontend-deploy”De twee frontends — beheerportaal en gebruikersportaal — zijn React 19 + Vite + TypeScript en worden gebouwd tot statische bundels (vite build). Die bundels worden als statische assets geserveerd (CDN of object-storage met statische hosting) en praten via HTTPS met de ene API. Omdat het statische bestanden zijn, is de hosting goedkoop en triviaal schaalbaar. De API-client is uit OpenAPI gegenereerd en getypeerd; details staan bij Frontend.
Documentatiesite (apart gedeployed)
Section titled “Documentatiesite (apart gedeployed)”Deze documentatiesite (docs/) is een Astro Starlight-project en wordt volledig los van de applicatie uitgerold als statische site. Ze deelt geen runtime met de API.
cd docsnpm installnpm run build # productie-build naar docs/dist/ (statische site)npm run preview # gebouwde site lokaal nakijkenDe build (npm run build) levert een statische docs/dist/-map op die op elke statische host kan staan. De build faalt bij kapotte interne links of renderfouten en dient zo ook als controle. Diagrammen zijn Mermaid en renderen automatisch (geen Java/PlantUML nodig).
De documentatie is de bron van waarheid voor het domeinmodel en de termen. Haar deploy-cyclus staat volledig los van die van de applicatie.
Meerdere omgevingen (Clermond-2, issue #24)
Section titled “Meerdere omgevingen (Clermond-2, issue #24)”(Per 2026-07-14b, ADR-044; vervangt het voorstel uit ADR-039 deel 2.) Er draaien twee omgevingen naast elkaar in één resourcegroep Clermond-2, elk met een omgevingslabel (slug) in alle resourcenamen en een eigen custom domein:
| Omgeving | Pulumi-stack | Slug | Domein | Deployt vanaf branch |
|---|---|---|---|---|
| Testing | testing | test | advies-4-mij.nl | main (elke push) |
| Productie | productie | prod | advice-4-me.com | productie (promotie = merge van main) |
De oude, handmatig opgezette productie in resourcegroep Clermond (gecodificeerd in de legacy-stack production) blijft ongemoeid tot ze handmatig wordt opgeruimd.
Custom domains per omgeving
Section titled “Custom domains per omgeving”| Hostnaam | Wijst naar |
|---|---|
<domein> (apex) + www.<domein> | Gebruikersportaal (Static Web App) |
beheer.<domein> | Beheerportaal (Static Web App) |
api.<domein> | API (App Service, gratis managed certificate) |
docs.<domein> | Documentatiesite (Static Web App, wachtwoordbeveiligd) |
noreply@<domein> | E-mailafzender (ACS custom email domain, SPF/DKIM in de zone) |
Persistente DNS vs. omgevingsresources
Section titled “Persistente DNS vs. omgevingsresources”De infrastructuur is gesplitst in twee Pulumi-projecten (zie infra/README.md):
infra/dns/(projectiseeyou-dns) — de persistente laag per domein: de Azure DNS-zone en het ACS-e-maildomein (met DKIM/SPF/verificatie-records). De nameservers zijn éénmalig bij de registrar (Vimexx) gedelegeerd; dit project kent bewust geen destroy en de kernresources zijnprotect: true.infra/(projectiseeyou-infra) — de omgeving zelf (App Service, PostgreSQL, ACR, Static Web Apps, monitoring, Communication Services) plus de omgevingsgebonden DNS-records en custom-domain-bindings in de bestaande zone. Deze laag gaat metpulumi destroy/up(workflowinfra.yml) volledig op en neer zonder de zone of de delegatie te raken — de kern van de “goedkoop up/down”-eis. Een draaiende omgeving kost ±€35–40/mnd; een neergehaalde alleen de zone (±€0,50/mnd).
De pijplijn (ci.yml) bepaalt per branch de doelomgeving (resourcenamen, VITE_API, health-URL) en deployt alleen de onderdelen waarvan bestanden wijzigden. Database-migraties blijven per omgeving apart: elke omgeving heeft een eigen PostgreSQL-server; er wordt nooit data tussen omgevingen gedeeld of gekopieerd.
Samengevat
Section titled “Samengevat”| Onderdeel | Deploy-vorm | Schaalt |
|---|---|---|
| API | Docker-image op serverless-container-platform (scale-to-zero + autoscaling) | Automatisch op verkeer |
| PostgreSQL | Managed service | Managed |
| Object-storage | Managed S3-compatibel via IMediaStore | Vrijwel onbeperkt |
| Redis | Optionele SignalR-backplane bij meerdere instances | Bij scale-out |
| Frontend-apps | Statische bundels (CDN/object-storage) | Statisch |
| Documentatiesite | Aparte statische site (npm run build) | Statisch |
Alle bouwstenen zijn gestandaardiseerd en vervangbaar: er is geen cloud-lock-in, de startkosten zijn laag dankzij scale-to-zero, en de stateless API maakt horizontaal opschalen bij pieken mogelijk. De definitieve platform- en regio-keuze hoort in de Beslissingen (ADR’s).