GitHub Actions vs GitLab CI voor backend, web en mobiel
GitHub Actions vs GitLab CI vergeleken voor monorepo's: runner-setup, geheimbeheer, caching en praktische pijplijnpatronen voor backend, web en mobiel.

Waar mensen mee worstelen in multi-app CI
Als één repo een backend, een webapp en mobiele apps bouwt, is CI niet langer "alleen tests draaien". Het wordt een verkeersleider voor verschillende toolchains, verschillende buildtijden en verschillende release-regels.
De meest voorkomende pijn is eenvoudig: één kleine wijziging triggert te veel werk. Een docs-edit start iOS-signing, of een backend-aanpassing dwingt een volledige webrebuild af, en plots voelt elke merge traag en risicovol.
In multi-app opstellingen komen een paar problemen vroeg naar boven:
- Runner drift: SDK-versies verschillen tussen machines, waardoor builds anders werken in CI dan lokaal.
- Geheimenverspreiding: API-sleutels, signing-certificaten en store-credentials worden gedupliceerd over jobs en omgevingen.
- Cacheverwarring: de verkeerde cachekey veroorzaakt verouderde builds, maar geen cache maakt alles pijnlijk traag.
- Gemengde release-regels: backends willen frequente deploys, terwijl mobiele releases geborgd zijn en extra checks nodig hebben.
- Leesbaarheid van pijplijnen: configuratie groeit uit tot een muur van jobs waar niemand nog aan wil zitten.
Daarom doet de keuze tussen GitHub Actions en GitLab CI er meer toe in monorepo's dan in single-app projecten. Je hebt duidelijke manieren nodig om werk per pad te splitsen, artifacts veilig te delen en parallelle jobs niet over elkaar heen te laten lopen.
Een praktische vergelijking komt neer op vier dingen: runner-setup en opschaling, geheimenopslag en -scope, caching en artifacts, en hoe makkelijk het is om "alleen te bouwen wat veranderd is" uit te drukken zonder de pijplijn tot een fragiele regelsoep te maken.
Dit gaat over dagelijkse betrouwbaarheid en onderhoudbaarheid, niet over welk platform meer integraties of een mooiere UI heeft. Het vervangt ook geen keuzes over je buildtools (Gradle, Xcode, Docker, enz.). Het helpt je het CI-platform te kiezen dat structuur eenvoudiger houdt.
Hoe GitHub Actions en GitLab CI zijn opgebouwd
Het grootste verschil is hoe elk platform pijplijnen en hergebruik organiseert, en dat begint pas te tellen zodra backend, web en mobile builds dezelfde repo delen.
GitHub Actions slaat automatisering op in YAML-bestanden onder .github/workflows/. Workflows triggeren op events zoals pushes, pull requests, schema's of handmatige runs. GitLab CI draait om .gitlab-ci.yml in de repo-root, met optionele include-bestanden; pijplijnen lopen typisch op pushes, merge requests, schema's en handmatige jobs.
GitLab is gebouwd rond stages. Je definieert stages (build, test, deploy) en wijst jobs aan die in die volgorde draaien. GitHub Actions is opgebouwd uit workflows met jobs. Jobs draaien standaard parallel, en je voegt afhankelijkheden toe waar iets moet wachten.
Voor het uitvoeren van dezelfde logica over veel targets zijn GitHub's matrix-builds een natuurlijke keuze (iOS vs Android, meerdere Node-versies). GitLab kan vergelijkbare fan-out doen met parallelle jobs en variabelen, maar vaak moet je meer onderdelen zelf aan elkaar knopen.
Hergebruik ziet er ook anders uit. In GitHub vertrouwen teams vaak op reusable workflows en composite actions. In GitLab komt hergebruik vaak via include, gedeelde templates en YAML-anchors/extends.
Goedkeuringen en beschermde omgevingen verschillen ook. GitHub gebruikt vaak protected environments met vereiste reviewers en environment secrets zodat productie-deploys pauzeren tot goedkeuring. GitLab combineert vaak protected branches/tags, protected environments en handmatige jobs zodat alleen specifieke rollen een deploy kunnen uitvoeren.
Runner-setup en jobuitvoering
Runner-setup is waar de twee platforms in dagelijks gebruik anders beginnen aan te voelen. Beide kunnen jobs draaien op hosted runners (jij beheert de machine niet) of self-hosted runners (jij beheert machine, updates en security). Hosted runners zijn makkelijker om mee te beginnen; self-hosted runners zijn vaak nodig voor snelheid, speciale tools of toegang tot interne netwerken.
Een praktische splitsing in veel teams is Linux-runners voor backend en web, en macOS-runners alleen wanneer je iOS moet bouwen. Android kan op Linux draaien, maar is zwaar, dus runnergrootte en schijfruimte doen ertoe.
Hosted vs self-hosted: wat je beheert
Hosted runners zijn een goede keuze als je een voorspelbare setup zonder onderhoud wilt. Self-hosted runners zinvol wanneer je specifieke Java/Xcode-versies nodig hebt, snellere caches of interne netwerktoegang.
Als je self-hosted kiest, definieer runner-rollen vroeg. De meeste repos doen het goed met een klein setje: een algemene Linux-runner voor backend/web, een krachtigere Linux-runner voor Android, een macOS-runner voor iOS-packaging en signing, en een aparte runner voor deploy-jobs met strakkere permissies.
De juiste runner per job kiezen
Beide systemen laten je runners targeten (labels in GitHub, tags in GitLab). Houd naamgeving gekoppeld aan workloads, zoals linux-docker, android, of macos-xcode15.
Isolatie is waar veel flakkerende builds vandaan komen. Achtergebleven bestanden, gedeelde caches die corrupt raken, of tools die "handmatig" op een self-hosted machine zijn geïnstalleerd kunnen willekeurige fouten veroorzaken. Schone workspaces, vastgezette toolversies en geplande runner-cleanups betalen zich meestal snel terug.
Capaciteit en permissies zijn andere terugkerende pijnpunten, vooral met macOS-beschikbaarheid en -kosten. Een goed uitgangspunt: build-runners mogen bouwen, deploy-runners mogen deployen, en productietokens leven in de kleinste mogelijke set jobs.
Geheimen en omgevingsvariabelen
Geheimen zijn waar multi-app CI-pijplijnen risicovol worden. De basis is vergelijkbaar (bewaar geheimen op het platform, injecteer ze tijdens runtime), maar scoping voelt anders.
GitHub Actions scopeert geheimen doorgaans op repository- en organisatieniveau, met een extra Environment-laag. Die Environment-laag is handig als productie een handmatige poort en een aparte set waarden van staging nodig heeft.
GitLab CI gebruikt CI/CD-variabelen op project-, groeps- of instanceniveau. Het ondersteunt ook environment-gescopeerde variabelen en beschermingen zoals "protected" (alleen beschikbaar op beschermde branches/tags) en "masked" (verborgen in logs). Die controles zijn nuttig wanneer één monorepo meerdere teams bedient.
De belangrijkste fout is accidentele blootstelling: debug-output, een mislukte opdracht die variabelen echoot, of een artifact dat per ongeluk een configfile bevat. Behandel logs en artifacts als standaard deelbaar.
In backend + web + mobile pijplijnen omvatten geheimen meestal cloud-credentials, database-URLs en third-party API-keys, signingmateriaal (iOS-certificaten/profiles, Android-keystore en wachtwoorden), registry-tokens (npm, Maven, CocoaPods) en automatiseringstokens (e-mail/SMS-providers, chatbots).
Voor meerdere omgevingen (dev, staging, prod) houd namen consistent en verwissel waarden via environment-scope in plaats van jobs te kopiëren. Dat houdt rotatie en toegangscontrole beheersbaar.
Een paar regels voorkomen de meeste incidenten:
- Geef de voorkeur aan kortlevende credentials (zoals OIDC naar cloudproviders wanneer beschikbaar) boven langlevende sleutels.
- Gebruik least privilege: scheid deploy-identiteiten voor backend, web en mobiel.
- Masker geheimen en vermijd het printen van omgevingsvariabelen, zelfs bij fouten.
- Beperk productiegeheimen tot beschermde branches/tags en vereiste reviewers.
- Sla nooit geheimen op in build-artifacts, zelfs niet tijdelijk.
Een eenvoudig, effectief voorbeeld: mobiele jobs zouden signing-secret alleen moeten krijgen op getagde releases, terwijl backend-deploy-jobs een gelimiteerde deploy-token op merges naar main kunnen gebruiken. Die wijziging alleen al verkleint de blast radius als een job verkeerd geconfigureerd is.
Caching en artifacts voor snellere builds
De meeste trage pijplijnen zijn traag om één saaie reden: ze downloaden en rebuilden steeds dezelfde dingen. Caching voorkomt herhaald werk. Artifacts lossen een ander probleem op: de exacte outputs van een specifieke run bewaren.
Wat je cachet hangt af van wat je bouwt. Backends profiteren van dependency- en compiler-caches (bijvoorbeeld de Go module cache). Web-builds profiteren van package manager-caches en buildtool-caches. Mobiele builds hebben vaak Gradle plus Android SDK-caching op Linux nodig, en CocoaPods of Swift Package Manager-caches op macOS. Wees terughoudend met agressieve iOS-buildoutput-caching (zoals DerivedData) tenzij je de afwegingen begrijpt.
Beide platforms volgen hetzelfde basispatroon: restore cache aan het begin van een job, en save de geüpdatete cache aan het einde. Het dagelijkse verschil is controle. GitLab maakt cache- en artifactgedrag expliciet in één bestand, inclusief expiratie. GitHub Actions leunt vaak op aparte actions voor caching, wat flexibel is maar makkelijker mis te configureren.
Cachekeys zijn belangrijker in monorepo's. Goede keys veranderen als inputs veranderen en blijven anders stabiel. Lockfiles (go.sum, pnpm-lock.yaml, yarn.lock en vergelijkbare) zouden de key moeten aansturen. Het helpt ook om een hash van de specifieke app-map die je bouwt op te nemen in plaats van de hele repo, en om aparte caches per app te houden zodat één wijziging niet alles invalideert.
Gebruik artifacts voor de deliverables die je van die specifieke run wilt bewaren: release-bundles, APK/IPA-outputs, testrapporten, coverage-bestanden en build-metadata. Caches zijn snelheidsverbeteringen; artifacts zijn dossiers.
Als builds nog steeds traag zijn, zoek dan naar te grote caches, keys die elke run veranderen (tijdstempels en commit SHAs zijn veelvoorkomende boosdoeners), en gecachte buildoutputs die niet herbruikbaar zijn op verschillende runners.
Monorepo fit: meerdere pijplijnen zonder chaos
Een monorepo wordt rommelig als elke push backend-tests, web-builds en mobile-signing triggert, ook al heb je alleen een README veranderd. Het schone patroon is: detecteer wat veranderd is en draai alleen de jobs die ertoe doen.
In GitHub Actions betekent dit vaak aparte workflows per app met path-filters zodat elke workflow alleen draait wanneer bestanden in zijn gebied veranderen. In GitLab CI betekent het vaak één pipelinebestand met rules:changes (of child pipelines) om jobgroepen aan te maken of over te slaan op basis van paden.
Gedeelde packages zijn waar vertrouwen breekt. Als packages/auth verandert, moeten zowel backend als web mogelijk opnieuw bouwen, zelfs als hun mappen niet veranderd zijn. Behandel gedeelde paden als triggers voor meerdere pijplijnen en houd afhankelijkheidsgrenzen duidelijk.
Een eenvoudige triggerkaart die verrassingen beperkt:
- Backend-jobs draaien bij wijzigingen in
backend/**ofpackages/**. - Web-jobs draaien bij wijzigingen in
web/**ofpackages/**. - Mobile-jobs draaien bij wijzigingen in
mobile/**ofpackages/**. - Docs-only wijzigingen draaien een snelle check (formatting, spellcheck).
Paralleliseer wat veilig is (unit tests, linting, web build). Seriëleer wat beheerst moet worden (deployments, app store releases). Zowel GitLab's needs als GitHub job-dependencies helpen je om snelle checks vroeg te draaien en de rest te stoppen als ze falen.
Houd mobile signing geïsoleerd van dagelijkse CI. Zet signing keys in een aparte environment met handmatige goedkeuring en voer signing alleen uit op getagde releases of een beschermde branch. Pull requests kunnen unsigned apps bouwen voor validatie zonder gevoelige credentials bloot te stellen.
Stap voor stap: een schone pijplijn voor backend, web en mobiel
Een schone multi-app pijplijn begint met naamgeving die intentie duidelijk maakt. Kies één patroon en houd je eraan zodat mensen logs kunnen scannen en weten wat er draaide.
Een schema dat leesbaar blijft:
- Pijplijnen:
pr-checks,main-build,release - Omgevingen:
dev,staging,prod - Artifacts:
backend-api,web-bundle,mobile-debug,mobile-release
Houd jobs klein en promoot alleen wat eerder checks heeft doorstaan:
-
PR checks (elke pull request): draai snelle tests en lint alleen voor de apps die veranderd zijn. Voor backend, bouw een deployable artifact (een container image of serverbundle) en bewaar het zodat latere stappen het niet opnieuw hoeven te bouwen.
-
Web build (PR + main): bouw de webapp naar een statische bundle. In PRs bewaar de output als artifact (of deploy naar een previewomgeving als je die hebt). Op main produceer je een versieerde bundle geschikt voor
devofstaging. -
Mobile debug builds (alleen PR): bouw een debug APK/IPA. Sign niet voor release. Het doel is snelle feedback en een bestand dat testers kunnen installeren.
-
Release builds (alleen tags): wanneer een tag zoals
v1.4.0wordt gepusht, draai volledige backend- en web-builds plus signed mobile release-builds. Genereer store-ready outputs en bewaar releasenotities bij de artifacts. -
Handmatige goedkeuringen: zet goedkeuringen tussen
stagingenprod, niet vóór basis-tests. Ontwikkelaars kunnen builds triggeren, maar alleen goedgekeurde rollen mogen naar productie deployen en productiegeheimen gebruiken.
Veelvoorkomende fouten die tijd verspillen
Teams verliezen vaak weken aan workflowgewoonten die langzaam flakkerende builds creëren.
Een valkuil is te veel vertrouwen op gedeelde runners. Als veel projecten om dezelfde pool concurreren, krijg je willekeurige timeouts, trage jobs en mobiele builds die alleen falen tijdens piekuren. Als backend, web en mobile belangrijk zijn, isoleer zware jobs op dedicated runners (of op zijn minst aparte wachtrijen) en maak resource-limieten expliciet.
Geheimen zijn ook een tijdverspiller. Mobile signing keys en certificaten zijn makkelijk verkeerd te behandelen. Een veelgemaakte fout is ze te breed op te slaan (beschikbaar voor elke branch en job) of ze te lekken via verhalende logs. Beperk signingmateriaal tot beschermde branches/tags en vermijd stappen die secret-waarden printen (zelfs base64-strings).
Caching kan averechts werken als teams enorme directories cachen of caches en artifacts verwarren. Cache alleen stabiele inputs. Bewaar outputs die je later nodig hebt als artifacts.
Tot slot, in monorepo's verbrandt het triggeren van elke pijplijn bij elke wijziging minuten en geduld. Als iemand een README aanpast en je bouwt iOS, Android, backend en web opnieuw, verliezen mensen vertrouwen in CI.
Een korte checklist die helpt:
- Gebruik padgebaseerde regels zodat alleen getroffen apps draaien.
- Scheid testjobs van deployjobs.
- Houd signing keys beperkt tot release-workflows.
- Cache kleine, stabiele inputs, niet hele buildmappen.
- Plan voorspelbare runner-capaciteit voor zware mobiele builds.
Snelle checks voordat je voor één platform kiest
Voordat je kiest, doe een paar checks die reflecteren hoe je echt werkt. Ze besparen je van het kiezen van een tool die prima voelt voor één app maar pijnlijk wordt zodra je mobiele builds, meerdere omgevingen en releases toevoegt.
Focus op:
- Runnerplan: hosted, self-hosted, of een mix. Mobiele builds duwen teams vaak naar een mix omdat iOS macOS nodig heeft.
- Geheimenplan: waar geheimen leven, wie ze kan lezen en hoe rotatie werkt. Productie moet strakker zijn dan staging.
- Cacheplan: wat je cachet, waar het wordt opgeslagen en hoe keys gevormd worden. Als de key elke commit verandert, betaal je de kosten zonder snelheid te winnen.
- Monorepoplan: padfilters en een nette manier om gedeelde stappen (lint, tests) te delen zonder copy-paste.
- Releaseplan: tags, goedkeuringen en scheiding van omgevingen. Wees expliciet over wie naar productie mag promoveren en welk bewijs ze nodig hebben.
Pressure-test die antwoorden met een klein scenario. In een monorepo met een Go-backend, een Vue-webapp en twee mobiele apps: een docs-only wijziging zou bijna niets moeten doen; een backend-wijziging zou backend-tests en het bouwen van een API-artifact moeten draaien; een mobiele UI-wijziging zou alleen Android en iOS moeten bouwen.
Als je die flow niet op één pagina kunt beschrijven (triggers, caches, geheimen, goedkeuringen), draai dan een éénweekse pilot op beide platforms met dezelfde repo. Kies degene die saai en voorspelbaar aanvoelt.
Voorbeeld: een realistische monorepo build- en release-flow
Stel je één repo voor met drie mappen: backend/ (Go), web/ (Vue) en mobile/ (iOS en Android).
Dagelijks wil je snelle feedback. Bij releases wil je volledige builds, signing en publish-stappen.
Een praktische splitsing:
- Feature branches: draai lint + unit tests voor de veranderde delen, bouw backend en web, en optioneel een Android debug-build. Sla iOS over tenzij het echt nodig is.
- Release tags: draai alles, maak versieerde artifacts, sign mobiele apps en push images/binaries naar je release-opslag.
De keuze van runners verandert zodra mobiel erbij komt. Go- en Vue-builds zijn blij op Linux vrijwel overal. iOS vereist macOS-runners, wat de beslissing vaak meer beïnvloedt dan iets anders. Als je team volledige controle over buildmachines wil, kan GitLab CI met self-hosted runners makkelijker zijn om als fleet te runnen. Als je minder ops-werk en snelle setup wilt, zijn GitHub hosted runners handig, maar macOS-minuten en beschikbaarheid worden onderdeel van je planning.
Caching is waar echte tijd wordt bespaard, maar de beste cache verschilt per app. Voor Go: cache module-downloads en de build-cache. Voor Vue: cache de package manager-store en rebuild alleen wanneer lockfiles veranderen. Voor mobiel: cache Gradle en de Android SDK op Linux; cache CocoaPods of Swift Package Manager op macOS, en verwacht grotere caches en meer invalidatie.
Een beslisregel die standhoudt: als je code al op één platform gehost is, start daar. Schakel alleen als runners (vooral macOS), permissies of compliance je dwingen.
Volgende stappen: kies, standaardiseer en automatiseer veilig
Kies het gereedschap dat past bij waar je code en mensen al zijn. Meestal toont het verschil zich in dagelijkse frictie: reviews, permissies en hoe snel iemand een kapotte build kan diagnosticeren.
Begin simpel: één pijplijn per app (backend, web, mobile). Zodra stabiel, haal gedeelde stappen naar herbruikbare templates zodat je copy-paste vermindert zonder eigenaarschap te vervagen.
Schrijf geheime scopes op zoals je zou opschrijven wie sleutels van een kantoor heeft. Productiegeheimen mogen niet voor elke branch leesbaar zijn. Stel een rotatiereminder in (elk kwartaal is beter dan nooit) en spreek af hoe noodrevoke werkt.
Als je bouwt met een no-code generator die echte broncode produceert, behandel generatie/export als een volwaardige CI-stap. Bijvoorbeeld, AppMaster (appmaster.io) genereert Go-backends, Vue3-webapps en Kotlin/SwiftUI mobiele apps, zodat je pijplijn code opnieuw kan genereren bij wijziging en daarna alleen de getroffen targets bouwt.
Zodra je een flow hebt waarin je team vertrouwen heeft, maak het de standaard voor nieuwe repos en houd het saai: duidelijke triggers, voorspelbare runners, strakke geheimen en releases die alleen draaien wanneer jij dat bedoelt.
FAQ
Standaard kun je het beste kiezen voor het platform waar je code en team al zitten. Schakel alleen als runners (vooral macOS), permissies of compliance je daartoe dwingen. De dagelijkse kosten zitten meestal in runnerbeschikbaarheid, geheimscope en hoe makkelijk je “alleen bouwen wat veranderd is” kunt uitdrukken zonder fragiele regels.
GitHub Actions voelt vaak eenvoudiger voor snelle setup en matrix-builds, met workflows verdeeld over meerdere YAML-bestanden. GitLab CI voelt vaak centraler en stages-gedreven, wat makkelijker te overzien is als de pijplijn groeit en je één plek wilt hebben om caches, artifacts en jobvolgorde te regelen.
Behandel macOS als een schaars goed en gebruik het alleen als je echt iOS-pakketten of signing nodig hebt. Een veelgebruikte baseline is Linux-runners voor backend en web, een zwaardere Linux-runner voor Android, en een macOS-runner gereserveerd voor iOS-jobs, plus een aparte deploy-runner met strakkere permissies.
Runner drift ontstaat als dezelfde job anders werkt doordat SDK's en tools verschillen tussen machines. Los dit op door toolversies vast te zetten, handmatige installs op self-hosted runners te vermijden, schone workspaces te gebruiken en periodiek runner-images op te schonen of opnieuw te bouwen zodat je geen onzichtbare verschillen ophoopt.
Maak geheimen alleen beschikbaar voor de kleinste set jobs die ze echt nodig hebben en houd productiesecreties achter beschermde branches/tags plus goedkeuringen. Voor mobiel is het veiligste uitgangspunt om signingmateriaal alleen te injecteren bij getagde releases; pull requests kunnen unsigned debug-builds maken voor validatie.
Gebruik caches om herhaald werk te versnellen en artifacts om exacte outputs van een specifieke run te bewaren. Een cache is een best-effort snelheidsverbetering en kan over tijd veranderen; een artifact is een bewaard deliverable zoals een buildbundle, testrapport of een release APK/IPA die je wilt kunnen terugvinden.
Baseer cachekeys op stabiele inputs zoals lockfiles en scope ze naar het deel van de repo dat je bouwt, zodat niet-gerelateerde wijzigingen niet alles ongeldig maken. Vermijd keys die elke run veranderen (zoals tijdstempels of volledige commit-SHA's) en houd aparte caches per app zodat backend, web en mobiel elkaar niet in de weg zitten.
Gebruik padgebaseerde regels zodat docs of niet-gerelateerde mappen geen dure jobs starten, en behandel gedeelde packages expliciet als triggers voor de apps die ervan afhankelijk zijn. Als een shared map verandert, is het prima om meerdere targets te herbouwen, maar maak die mapping bewust zodat de pijplijn voorspelbaar blijft.
Houd signing keys en store-credentials buiten de dagelijkse CI-runs door ze achter tags, beschermde branches en goedkeuringen te plaatsen. Voor pull requests bouw je debug-varianten zonder release-signing zodat je snelle feedback krijgt zonder gevoelige credentials bloot te stellen.
Ja, maar behandel generatie als een volwaardige stap met duidelijke inputs en outputs zodat het makkelijk te cachen en voorspelbaar te herhalen is. Als je een tool als AppMaster gebruikt die echte broncode genereert, kun je genereren bij relevante wijzigingen en daarna alleen de daadwerkelijk beïnvloede targets bouwen (backend, web of mobiel).


