eDB: Uitdagingen en oplossingen

By Elias De Bock

eDB: Uitdagingen en oplossingen
11 juni 2025

eDB is mijn meest ambitieuze project tot nu toe. Een schaalbaar platform waar gebruikers een account kunnen aanmaken, zich kunnen abonneren op verschillende demo-applicaties uit de catalogus en de software die ik gebouwd heb kunnen uitproberen. De eerste demo-app in deze catalogus is een webshop. Neem gerust een kijkje:


Uitdagingen & oplossingen

Het bouwen van dit platform bracht een aantal obstakels met zich mee. Laten we even inzoomen op vier punten en hoe ik hiermee omga op dit moment.

  • Schaalbaarheid → Docker & Kubernetes
  • Hosting → Hetzner VPS
  • Architectuur → Monolith naar een Gelaagde Modulaire Monolith
  • CI/CD Pipelines → Github Actions

Schaalbaarheid

Toen ik mijn eerste tutorialproject op Netlify zette, vroeg ik me af: Wat gebeurt er eigenlijk met mijn code na het deployen? Hoe schaal je zo'n project op als er plots meer gebruikers komen? Wat draait er onder de motorkap?

Om die vragen te beantwoorden én eDB echt schaalbaar te maken, heb ik de stap gezet naar Kubernetes (K3s). Mijn plan was om eerst alles zelf te hosten, en daarna pas over te stappen naar cloud-oplossingen. Ik wilde de volledige infrastructuur zelf beheren om diepgaand inzicht te krijgen in Linux-servers, Docker en container orchestration. Door deze hands-on aanpak leerde ik alles van TCP en poorten tot services, secrets, config maps, deployments en pods.

Waarom Docker?

Docker maakt het mogelijk om applicaties en al hun dependencies te verpakken in één container image — een self-contained unit die overal hetzelfde werkt.

Elk onderdeel van eDB (zoals platform-api, admin-app, enz.) heeft een eigen Dockerfile, waarmee ik images bouw. Die images worden vervolgens gepusht naar mijn container registry, zodat ik ze in Kubernetes kan gebruiken bij het uitrollen van de juiste versie per component. Je vindt mijn publieke registry op Docker Hub.

Met Docker krijg ik:

  • Reproduceerbare builds – geen "werkt op mijn machine"-problemen
  • Snelle lokale tests van nieuwe features of fixes
  • Consistente omgeving tussen ontwikkeling, staging en productie

Aanvankelijk gebruikte ik Docker Compose om de verschillende services lokaal te coördineren. Uiteindelijk zette ik de stap naar Kubernetes, waarmee ik mijn infrastructuur beter kon schalen en automatiseren.

Waarom Kubernetes?

Mijn interesse in Kubernetes begon al in 2020, aangewakkerd door Brecht tijdens onze knowledge sharing sessies. Destijds was het nog te vroeg om ermee aan de slag te gaan en was mijn ervaring beperkt tot Docker. Een aantal maanden geleden was het moment daar om het in de praktijk te brengen.

Kubernetes stelt me in staat om mijn applicaties betrouwbaar, herhaalbaar en schaalbaar te draaien. Dankzij K3s, een lightweight distributie van Kubernetes, kan ik mijn volledige platform draaien op een eigen VPS zonder onnodige overhead.

Met Kubernetes kan ik:

  • Containers automatisch schalen op basis van load
  • Zero-downtime deploys uitvoeren via rolling updates
  • Fouten automatisch herstellen, bijvoorbeeld door een container opnieuw te starten na een crash
  • Persistent storage en secrets beheren zonder handmatige OS-configuratie

Door Kubernetes als orchestrator te gebruiken, heb ik een platform gecreëerd waarop ik modulair kan uitbreiden met nieuwe microservices of apps, zonder telkens opnieuw infrastructuur op te zetten. Dit past perfect bij mijn visie om eDB als platform te laten groeien met meerdere applicaties en diensten.

Diagram 1: Inkomend verkeer via Cloudflare & NGINX

Deze visual toont hoe verkeer van de buitenwereld via Cloudflare wordt geleid naar mijn NGINX Ingress Controller binnen de K3s-cluster.

Cloudflare fungeert als reverse proxy: dit betekent dat alle inkomende verzoeken naar bijvoorbeeld app.eliasdebock.com of api.eliasdebock.com eerst bij Cloudflare toekomen, en pas daarna — afhankelijk van de routingregels — worden doorgestuurd naar de achterliggende server (in dit geval mijn VPS bij Hetzner).

🔁 Wat doet een reverse proxy?

Een reverse proxy staat voor je server en handelt inkomende HTTP-verzoeken af namens die server. Het maskeert de werkelijke locatie (IP-adres) van je server en biedt tegelijkertijd:

  • SSL-terminatie (Cloudflare beheert het HTTPS-certificaat)
  • DDoS-bescherming
  • Caching van statische content
  • Bot-filtering en firewall-regels

☁️ Hoe werkt de koppeling met Hetzner?

In Cloudflare configureer ik DNS-records (type A of CNAME) die verwijzen naar het publieke IP-adres van mijn Hetzner VPS. Deze records staan op "proxy mode" (het oranje wolkje in de Cloudflare UI), wat betekent dat Cloudflare als tussenstation optreedt:

app.eliasdebock.com
Cloudflare Proxy
188.245.228.20 (Hetzner VPS)

Op mijn Hetzner VPS draait een Kubernetes-cluster (K3s) met een NGINX Ingress Controller. Cloudflare stuurt verkeer daarnaartoe, en de Ingress Controller bepaalt — op basis van hostnames en URL-paden — welke service (zoals admin-app, platform-api, enzovoort) het verkeer verder mag verwerken.

🚪 Hoe werkt de NGINX Ingress Controller?

De NGINX Ingress Controller is een component binnen Kubernetes die HTTP(S)-verkeer beheert op basis van de Ingress resources die je zelf definieert. Deze controller leest alle Ingress-regels in je cluster en genereert daarmee een dynamisch NGINX-configuratiebestand. Dit bepaalt:

  • Welke hostnames en paden naar welke Kubernetes-services worden doorgestuurd
  • Hoe TLS-certificaten worden afgehandeld (bijvoorbeeld via cert-manager)
  • Welke security- en rewrite-regels toegepast moeten worden (zoals path rewrites of redirect naar HTTPS)

Zodra verkeer van Cloudflare aankomt op de VPS, ontvangt de NGINX Ingress Controller het verzoek. Op basis van het domein en pad (/admin, /platform, enz.) stuurt de controller het verkeer door naar de juiste interne service. Dit alles gebeurt zonder dat je manueel webserverconfiguraties hoeft bij te houden.

Op deze manier blijft mijn infrastructuur achter de schermen verborgen, terwijl ik toch veilige en schaalbare toegang tot mijn platform kan aanbieden.

Diagram 2: Interne Routing in de K3s Cluster

Deze tweede diagram illustreert hoe het verkeer binnen de K3s-cluster wordt afgehandeld na aankomst via Ingress.

Elke route die door de NGINX-controller is goedgekeurd, wordt doorgestuurd naar een specifieke Kubernetes service. Zo’n service fungeert als een stabiel intern netwerkadres binnen de cluster. Verkeer dat op een bepaalde poort binnenkomt (zoals :80 of :443), wordt via de service load-balanced verdeeld over één of meerdere achterliggende pods.

Wat doet een service precies?

In Kubernetes is een Service een abstractielaag bovenop een groep pods. Het zorgt ervoor dat:

  • je pods niet rechtstreeks hoeft aan te spreken (wat handig is, want pods kunnen crashen en opnieuw opstarten met een ander IP),
  • verkeer op een vaste poort naar de juiste backend wordt gestuurd,
  • Kubernetes intern weet welke pod verantwoordelijk is voor welke request.

De meest gebruikte servicetypes zijn:

TypeUitleg
ClusterIPAlleen bereikbaar binnen de cluster. Perfect voor interne communicatie.
NodePortOpent een poort op het IP van de node zelf. Handig voor eenvoudige toegang, minder flexibel.
LoadBalancerGebruikt in cloudomgevingen met externe load balancers. Niet nodig dankzij Cloudflare + Ingress.

In mijn setup gebruik ik voornamelijk ClusterIP, zodat alleen de Ingress Controller toegang heeft tot de interne services.

En wat doen poorten?

Elke service luistert op één of meerdere poorten (zoals 3000, 8080, enz.), en koppelt deze aan de containerpoorten binnenin de pod. Daardoor kan verkeer bijvoorbeeld lopen via:

Ingress → Service op poort 80 → Pod op poort 3000

De service fungeert dus als tussenlaag tussen het inkomende verkeer en de daadwerkelijke applicatiecode die draait in de pods.

Een poort is een genummerde ingang op een netwerkinterface waarop een applicatie of service luistert naar binnenkomend verkeer. Het is vergelijkbaar met een deur naar een specifieke functie binnen een server of container. Bijvoorbeeld:

  • Poort 80 is standaard voor HTTP-verkeer
  • Poort 443 is voor HTTPS
  • Applicaties kunnen custom poorten gebruiken zoals 3000 of 8080

In Kubernetes koppel je dus een servicepoort (zoals 80) aan een containerpoort (zoals 3000) zodat verkeer correct wordt doorgestuurd.

Wat is een Deployment?

Een Deployment in Kubernetes is een resource die ervoor zorgt dat een bepaalde set pods consistent en up-to-date draait. Een Deployment:

  • Beheert hoeveel instanties (replica's) van een applicatie actief moeten zijn
  • Start automatisch nieuwe pods als er eentje crasht (self-healing)
  • Voert rolling updates uit bij nieuwe versies zonder downtime
  • Is gekoppeld aan een service die het verkeer verdeeld over de actieve pods

In mijn setup bouw en push ik een Docker image per app. Die image wordt gebruikt door een Deployment om pods aan te maken. Het verkeer dat via de service binnenkomt, wordt dus uiteindelijk afgeleverd aan een of meerdere pods die onder controle staan van die Deployment.

Zo blijft het verkeer stabiel en schaalbaar, ook als het platform groeit of wanneer er fouten optreden.


Hosting op Hetzner VPS (ARM CAX21)

Hosting gebeurt momenteel op een VPS bij Hetzner – een kostenefficiënte oplossing die op dit moment aan mijn behoeften voldoet. De hosting voor zowel staging- als productieomgevingen draait op Ubuntu-servers, net als mijn eigen CI/CD-runner voor GitHub Actions.

Dit omvat:

  • API servers
  • Static file servers (NGINX)
  • Auth servers
  • Database servers
  • GitHub Actions self-hosted runner voor builds, tests en deploys

Door mijn CI/CD-pipeline zelf te hosten, behoud ik volledige controle over de buildomgeving en vermijd ik limieten of wachttijden van GitHub-hosted runners. Alles draait centraal op dezelfde VPS-infrastructuur, wat debuggen, testen en deployen efficiënt maakt.

Wat is een VPS?

Een VPS (Virtual Private Server) is een virtuele server die draait op fysieke hardware, maar is afgescheiden van andere gebruikers. Het biedt de voordelen van een eigen server, zoals root access en volledige controle over je softwarestack, zonder de kosten van dedicated hardware.

Met een VPS krijg ik:

  • Volledige vrijheid over het besturingssysteem (Ubuntu)
  • Eigen configuraties voor Docker, Kubernetes, databases, enz.
  • Lage kosten in vergelijking met managed cloud-oplossingen
  • Goede performance, zeker op ARM-gebaseerde servers zoals de CAX21

Mijn keuze voor Hetzner was gebaseerd op prijs/prestatie, IPv6 support, en de mogelijkheid om snel nieuwe volumes of backups toe te voegen.

☁️ Cloud Hosting Toekomst

Dit is echter geen eindstation. Op termijn wil ik overstappen naar managed cloudservices, zoals:

  • Azure Kubernetes Service (AKS)
  • Amazon Elastic Kubernetes Service (EKS)
  • Google Kubernetes Engine (GKE)

Deze platforms bieden:

  • Automatische schaalbaarheid
  • Self-healing infrastructuur
  • Monitoring, logging en autoscaling ingebouwd
  • Minder beheer, meer focus op development

Daarnaast zijn er serverless oplossingen zoals:

  • Azure App Service / Azure Functions
  • AWS Lambda / AWS Fargate

Met deze technologieën hoef ik me minder zorgen te maken over infrastructuurbeheer, terwijl eDB dynamisch kan schalen naargelang de belasting. Zo behoud ik maximale flexibiliteit én betrouwbaarheid voor de toekomst.


Van Monolith naar een Gelaagde Modulaire Monolith (Nx)

In eerste instantie was eDB opgezet als één grote monolithische applicatie — met een eenvoudige scheiding tussen frontend en backend. Naarmate het platform groeide, werd de nood aan een betere structuur steeds duidelijker.

Door de overstap naar Nx heb ik de frontend geherstructureerd tot een gelaagde modulaire monolith, waarbij de code wordt opgesplitst in herbruikbare en goed geteste libraries. Deze aanpak maakt het geheel schaalbaarder, overzichtelijker en sneller te ontwikkelen.

Client-side structuur (frontend)

De frontend is opgesplitst in verschillende types van Nx-libraries:

  • Feature libraries – Bevatten losse features zoals login, instellingen, dashboard, enz.
  • Data-access libraries – Voor het ophalen en manipuleren van externe data via HTTP.
  • Util libraries – Voor herbruikbare logica zoals authenticatie, logging en configuratie.
  • UI library – Componentenbibliotheek gebaseerd op Carbon Design System en Angular Material. Gedocumenteerd met Storybook.

Deze modulaire opbouw maakt het mogelijk om:

  • Elke module apart te testen (unit en visueel),
  • Enkel gewijzigde delen te bouwen of te deployen,
  • Een consistente UI/UX aan te houden over verschillende apps heen.

Dependency Graph

Bekijk hieronder een interactieve Nx dependency graph van alle frontend-libraries en hoe ze met elkaar verbonden zijn:

Snellere Builds met CI/CD en Nx

De combinatie van Nx en GitHub Actions zorgt ervoor dat enkel de relevante delen van het project worden gebouwd en getest. Dankzij het nx affected commando worden alleen de aangepaste modules meegenomen in de pipeline.

Dat betekent:

  • Snellere builds
  • Snellere tests
  • Snellere deploys

De volledige CI/CD-pipeline draait via een self-hosted GitHub Actions runner op dezelfde VPS, waardoor de volledige keten — van commit tot productie — onder eigen beheer en volledig geoptimaliseerd is.


CI/CD Pipelines

Om mijn staging- en productieomgevingen vlot bij te werken, heb ik een reeks CI/CD-pijplijnen opgezet. In deze sectie leg ik stap voor stap uit hoe dat proces werkt.

Werken met branches

Elke nieuwe feature begint met het aanmaken van een aparte tak:

  1. Start vanuit de dev-branch en haal de laatste wijzigingen op.
  2. Maak lokaal een feature-branch aan, werk aan je code, en open vervolgens een Pull Request om jouw branch te vergelijken met dev.

Pre-merge checks bij het openen van een PR

Zodra je een Pull Request opent, wordt er automatisch een pre-merge pipeline gestart. Deze pipeline:

  • Voert linting, tests en een build uit van enkel de aangetaste code (nx affected).
  • Bouwt Docker-images en pushed ze naar Docker Hub.

Pas wanneer al deze checks slagen, kun je veilig mergen naar de dev-branch. Dit zal automatisch de staging-deployment triggeren.

Development Workflow

Deployment naar staging (na merge)

Na een succesvolle merge naar dev wordt automatisch een pipeline gestart die de applicatie uitrolt naar de stagingomgeving, die de productieomgeving zo goed mogelijk nabootst.

Staging URLs:

Tweede PR openen: naar productie

Wanneer alles goed werkt in staging, open je een nieuwe Pull Request, dit keer om te mergen van dev naar main. Deze pipeline doet opnieuw:

  • Linting
  • Testing
  • Bouwen van enkel de relevante delen

Wanneer alles slaagt, kun je de PR mergen naar main.

Deployment naar productie

De merge naar main triggert automatisch een nieuwe pipeline die de build uitrolt naar de productieomgeving.

Productie URLs:

Onderstaande visual geeft een overzicht van alle pipelines die in dit project draaien:

Development Workflow

Je kan deze pipelines ook live volgen via GitHub, onder het Actions tabblad:

Staging Deployment Pipeline Github


Conclusie

eDB illustreert de kern van wat ik het liefst doe: end-to-end webapplicaties bouwen die schaalbaar, onderhoudbaar en gebruiksvriendelijk zijn. Door de inzet van Kubernetes, Nx en Keycloak kan ik snel nieuwe demo-applicaties toevoegen en de bestaande componenten blijven verbeteren. Wil je meer weten of heb je zelf ideeën voor een vergelijkbaar platform of samenwerking? Bekijk dan de code of stuur me gerust een bericht.

Veel plezier met het ontdekken van eDB!