Infrastructuur
Deze pagina beschrijft de AWS-infrastructuur van Remedice op hoofdlijnen. De beschrijving volgt de actuele hostingroadmap en de Terraform-modules in infra/.
Technische architectuur
Remedice draait in eu-central-1 en gebruikt gescheiden AWS-accounts voor staging en productie. De diagrammen hieronder tonen de productieomgeving. Staging bestaat als aparte omgeving met dezelfde module-opzet, maar heeft hier geen eigen diagram.
flowchart LR
subgraph prod["Prod account"]
foundation@{ img: "/assets/aws/aws-iam.svg", label: "foundation<br/>OIDC, Config, alerts", pos: "b", w: 46, h: 46, constraint: "on" }
network@{ img: "/assets/aws/aws-vpc.svg", label: "network<br/>VPC, subnets, SGs", pos: "b", w: 46, h: 46, constraint: "on" }
stateful@{ img: "/assets/aws/aws-rds.svg", label: "stateful<br/>Aurora, S3, KMS, ECR, secrets", pos: "b", w: 46, h: 46, constraint: "on" }
cdn@{ img: "/assets/aws/aws-cloudfront.svg", label: "cdn<br/>CloudFront app + media", pos: "b", w: 46, h: 46, constraint: "on" }
stateless@{ img: "/assets/aws/aws-ecs.svg", label: "stateless<br/>ALB, WAF, ECS services", pos: "b", w: 46, h: 46, constraint: "on" }
scrapers@{ img: "/assets/aws/aws-eventbridge.svg", label: "scraper-runner<br/>EventBridge + ECS tasks", pos: "b", w: 46, h: 46, constraint: "on" }
end
foundation --> network
network --> stateful
stateful --> cdn
stateful --> stateless
stateful --> scrapers
Publieke request path
flowchart LR
user[Gebruiker]
zorgid[ZORG-ID OIDC]
dns@{ img: "/assets/aws/aws-route53.svg", label: "Route 53", pos: "b", w: 46, h: 46, constraint: "on" }
subgraph edge["Edge"]
appCf@{ img: "/assets/aws/aws-cloudfront.svg", label: "CloudFront app", pos: "b", w: 46, h: 46, constraint: "on" }
mediaCf@{ img: "/assets/aws/aws-cloudfront.svg", label: "CloudFront media", pos: "b", w: 46, h: 46, constraint: "on" }
waf@{ img: "/assets/aws/aws-waf.svg", label: "WAF", pos: "b", w: 46, h: 46, constraint: "on" }
alb@{ img: "/assets/aws/aws-elb.svg", label: "ALB", pos: "b", w: 46, h: 46, constraint: "on" }
end
subgraph storage["S3 origins"]
frontend@{ img: "/assets/aws/aws-s3.svg", label: "S3 frontend", pos: "b", w: 46, h: 46, constraint: "on" }
media@{ img: "/assets/aws/aws-s3.svg", label: "S3 general", pos: "b", w: 46, h: 46, constraint: "on" }
end
api@{ img: "/assets/aws/aws-ecs.svg", label: "ECS backend", pos: "b", w: 46, h: 46, constraint: "on" }
user --> dns
user --> zorgid
zorgid --> alb
dns --> appCf --> frontend
dns --> mediaCf --> media
dns --> waf --> alb --> api
Applicatie en data
flowchart LR
api@{ img: "/assets/aws/aws-ecs.svg", label: "ECS backend", pos: "b", w: 46, h: 46, constraint: "on" }
worker@{ img: "/assets/aws/aws-ecs.svg", label: "Celery worker", pos: "b", w: 46, h: 46, constraint: "on" }
beat@{ img: "/assets/aws/aws-ecs.svg", label: "Celery beat", pos: "b", w: 46, h: 46, constraint: "on" }
redis@{ img: "/assets/aws/aws-elasticache.svg", label: "ElastiCache Redis", pos: "b", w: 46, h: 46, constraint: "on" }
subgraph data["Stateful resources"]
db@{ img: "/assets/aws/aws-rds.svg", label: "Aurora user-data", pos: "b", w: 46, h: 46, constraint: "on" }
buckets@{ img: "/assets/aws/aws-s3.svg", label: "S3 buckets", pos: "b", w: 46, h: 46, constraint: "on" }
secrets@{ img: "/assets/aws/aws-secrets-manager.svg", label: "Secrets Manager", pos: "b", w: 46, h: 46, constraint: "on" }
kms@{ img: "/assets/aws/aws-kms.svg", label: "KMS", pos: "b", w: 46, h: 46, constraint: "on" }
logs@{ img: "/assets/aws/aws-cloudwatch.svg", label: "CloudWatch Logs", pos: "b", w: 46, h: 46, constraint: "on" }
end
api --> db
worker --> db
api --> redis
worker --> redis
beat --> worker
api --> buckets
worker --> buckets
api --> secrets --> kms
buckets --> kms
api --> logs
worker --> logs
Achtergrondtaken en AI
flowchart LR
api@{ img: "/assets/aws/aws-ecs.svg", label: "ECS backend", pos: "b", w: 46, h: 46, constraint: "on" }
worker@{ img: "/assets/aws/aws-ecs.svg", label: "Celery worker", pos: "b", w: 46, h: 46, constraint: "on" }
schedule@{ img: "/assets/aws/aws-eventbridge.svg", label: "EventBridge Scheduler", pos: "b", w: 46, h: 46, constraint: "on" }
scraper@{ img: "/assets/aws/aws-ecs.svg", label: "ECS RunTask<br/>scraper-runner", pos: "b", w: 46, h: 46, constraint: "on" }
dlq@{ img: "/assets/aws/aws-sqs.svg", label: "SQS DLQ", pos: "b", w: 46, h: 46, constraint: "on" }
alert@{ img: "/assets/aws/aws-sns.svg", label: "SNS alert", pos: "b", w: 46, h: 46, constraint: "on" }
artifacts@{ img: "/assets/aws/aws-s3.svg", label: "S3 scraped-content", pos: "b", w: 46, h: 46, constraint: "on" }
bedrock@{ img: "/assets/aws/aws.svg", label: "Bedrock", pos: "b", w: 46, h: 46, constraint: "on" }
kb@{ img: "/assets/aws/aws.svg", label: "Bedrock KB", pos: "b", w: 46, h: 46, constraint: "on" }
vector@{ img: "/assets/aws/aws-rds.svg", label: "Aurora pgvector", pos: "b", w: 46, h: 46, constraint: "on" }
speech@{ img: "/assets/aws/aws.svg", label: "Transcribe", pos: "b", w: 46, h: 46, constraint: "on" }
ses@{ img: "/assets/aws/aws-ses.svg", label: "SES", pos: "b", w: 46, h: 46, constraint: "on" }
messagebird@{ img: "/assets/aws/message-text.svg", label: "MessageBird SMS", pos: "b", w: 46, h: 46, constraint: "on" }
schedule --> scraper
scraper --> artifacts --> kb --> vector
scraper -. failure .-> dlq --> alert
api --> bedrock
api --> kb
api --> speech
worker --> ses
worker --> messagebird
Kerncomponenten
- DNS en edge: Route 53 beheert
remedice.nlen de delegatie naarstaging.remedice.nl. CloudFront bedient de webapp en media. De API loopt via WAF en een Application Load Balancer. - Compute: Django, Daphne en Celery draaien op ECS Fargate. De prod-scrapers draaien via EventBridge als losse Fargate Spot-taken op een aparte scraper-cluster.
- Database: Aurora Serverless v2 PostgreSQL bevat de tenantdata. Remedice gebruikt
django-tenants, waarbij elke apotheek een eigen schema heeft. - Cache en queue: Redis ondersteunt Django Channels, caching en Celery. Productie gebruikt ElastiCache. Staging gebruikt een goedkopere Redis-task op Fargate.
- Opslag en encryptie: S3 bewaart media, frontendbestanden, chatarchieven, scraper-artifacts en auditdata. KMS versleutelt S3-objecten en gevoelige applicatiedata.
- Statische sites: de landingspagina (apex en
www), de documentatiesite en de contactpagina draaien als losse statische sites op S3 met CloudFront ervoor (modulestatic-site). Ze staan los van de webapp, hebben een eigen CloudFront-distributie met OAC en een eigen Content Security Policy, en kosten in rust vrijwel niets. - Berichtenverkeer: SES verstuurt e-mail vanaf het aparte afzenddomein
mail.remedice.nl, gescheiden van de Zoho-mailboxen op het hoofddomein. MessageBird (Bird) verstuurt sms (onder andere de tweede-factor- en uitnodigingscodes) onder de alfanumerieke afzender Remedice; deze externe partij is gekozen omdat AWS SNS nog geen sms-productietoegang heeft verleend, en SNS blijft als terugvaloptie geconfigureerd. Telefoonnummers worden bewust niet gelogd; foutsignalering loopt via de applicatie. - Secrets en configuratie: Secrets Manager bevat alleen gevoelige waarden, waaronder de OIDC- en OAuth-clientgegevens voor ZORG-ID, Google en Apple. Niet-geheime configuratie blijft in de settings en Terraform.
- ZORG-ID: VZVZ redirect naar de bestaande API-callbacks
https://api.staging.remedice.nl/api/auth/zorgid/callback/enhttps://api.remedice.nl/api/auth/zorgid/callback/. De backend haalt discovery, token en userinfo op viaacceptatie.zorg-id.nl/oauthofproductie.zorg-id.nl/oauth.
AWS AI-laag
De AI-stack voor patientdata blijft binnen AWS. Bedrock levert LLM-aanroepen en embeddings. De kennisbank gebruikt een aparte Aurora pgvector-database in het productieaccount. Staging kan deze kennisbank alleen lezen via cross-account rechten.
Transcribe verwerkt spraak naar tekst voor de anamnese. De tekst-naar-spraak van de werkafspraken-podcast loopt buiten AWS via Google Cloud Chirp 3 HD en verwerkt geen patientdata.
Omgevingen
- Staging: draait in een eigen AWS-account met dezelfde Terraform-modules als productie. De omgeving is bedoeld voor validatie van backend, frontend, CDN, database en CI/CD.
- Productie: draait in een apart AWS-account. De gebruikersroute blijft warm. Achtergrondtaken en AI-onderdelen worden waar mogelijk pay-per-use ingericht.