Ga naar inhoud

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.


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:

EisGevolg voor de deployment
Lage startkostenEen serverless-container-platform met scale-to-zero: bij weinig verkeer draaien er geen (betaalde) instances.
SchaalbaarheidAutoscaling op basis van verkeer; de API is stateless ontworpen, dus instances zijn vrij bij te schakelen.
Anonimiteit & tijdelijke mediaMedia in object-storage met een retentie-proces dat altijd draait; geen media in de database.
Geen cloud-lock-inAlleen 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).


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.

ComponentInvullingSchaalt mee
APIDe ISeeYou.Api-Docker-image (Minimal API + SignalR) op een serverless-container-platform.Ja — scale-to-zero + autoscaling.
Frontend-appsTwee statische React-bundels (beheerportaal + gebruikersportaal), zie Frontend.Statisch (CDN/object-storage).
DatabaseManaged PostgreSQL (Npgsql), zie Backend & API — Persistentie.Verticaal/managed.
Object-storageS3-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 (IsBeheerder vs. 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.


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")]
  1. 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.
  2. Docker build — de ISeeYou.Api-image bouwen (multi-stage).
  3. Image push — de image naar de container-registry pushen, getagd op commit/versie.
  4. 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 de Keuzelijst-seed. Dit is een aparte, bewuste stap, niet iets dat de app bij opstart impliciet doet.
  5. 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- en docker-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 naar main bouwt en rolt de pijplijn automatisch uit naar Azure — alleen de onderdelen waarvan bestanden wijzigden (app/backend/** → API, app/frontend|beheer|shared/** → portalen, docs/** → documentatiesite).

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


De API is hetzelfde image in elke omgeving; verschillen zitten in omgevingsvariabelen/secrets, niet in de build. De belangrijkste:

CategorieVoorbeelden
DatabasePostgreSQL-connectiestring (Npgsql).
Object-storageS3-endpoint, bucket, sleutel/secret voor IMediaStore.
RealtimeRedis-connectiestring (alleen bij meerdere instances).
ObservabilityAPPLICATIONINSIGHTS_CONNECTION_STRING (leeg → alleen console-logging), zie ADR-032.
BeveiligingArgon2id-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 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.

ServiceImage/rolProductie-tegenhanger
postgresPostgreSQLManaged PostgreSQL
minioS3-compatibele object-storageManaged S3-compatibele opslag
apiISeeYou.Api (Docker)API op serverless-container-platform
frontendVite-dev-server / statische bundelsCDN/object-storage
Terminal window
cd app
docker compose up # postgres + minio + api + frontend

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


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.


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.

Terminal window
cd docs
npm install
npm run build # productie-build naar docs/dist/ (statische site)
npm run preview # gebouwde site lokaal nakijken

De 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:

OmgevingPulumi-stackSlugDomeinDeployt vanaf branch
Testingtestingtestadvies-4-mij.nlmain (elke push)
Productieproductieprodadvice-4-me.comproductie (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.

HostnaamWijst 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)

De infrastructuur is gesplitst in twee Pulumi-projecten (zie infra/README.md):

  • infra/dns/ (project iseeyou-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 zijn protect: true.
  • infra/ (project iseeyou-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 met pulumi destroy/up (workflow infra.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.


OnderdeelDeploy-vormSchaalt
APIDocker-image op serverless-container-platform (scale-to-zero + autoscaling)Automatisch op verkeer
PostgreSQLManaged serviceManaged
Object-storageManaged S3-compatibel via IMediaStoreVrijwel onbeperkt
RedisOptionele SignalR-backplane bij meerdere instancesBij scale-out
Frontend-appsStatische bundels (CDN/object-storage)Statisch
DocumentatiesiteAparte 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).