
Andrew Hanna

Andrew Hanna

Salesforce development is ver verwijderd van handmatige Change Sets. Zodra teams groeien en vaker releasen, worden handmatige deployments snel een bottleneck.
Daarom is CI/CD (Continuous Integration and Continuous Delivery) belangrijk. Een goede pipeline valideert, test en promoot wijzigingen automatisch van development naar production.
In deze gids kijken we hoe Salesforce CI/CD werkt, hoe je een pipeline bouwt met GitHub Actions, en wanneer DIY automation genoeg is of juist te kwetsbaar wordt.
Nieuw in het vakgebied? Onze gids over Salesforce DevOps onder de knie krijgen behandelt de basis waarop deze pipeline voortbouwt.
Salesforce teams deployen geen gecompileerde app, maar metadata en configuratie zoals:
Apex classes en triggers
Lightning Web Components
Flows en automation
Profiles, permission sets, objects, fields en layouts
Zonder automation ontstaan snel drift tussen omgevingen, ontbrekende dependencies, handmatige fouten, weinig zicht op wat er echt is gewijzigd en slechte traceability tussen work items en releasecontent.
CI/CD pipelines lossen dit op door:
wijzigingen automatisch te valideren
tests te draaien vóór deployment
Git als source of truth te gebruiken
alleen goedgekeurde wijzigingen te promoten
omgevingen consistent te houden over tijd
Deze gids gaat uit van een repository en een set orgs die al op automatisering zijn ingericht. Ontbreekt er een, dicht dat gat dan eerst: een pipeline eroverheen automatiseert alleen het gat.
Metadata al onder versiebeheer, in source format. Alles wat je
wil deployen staat onder force-app en heeft een commit-historie. De
pipeline promoot commits, geen orgs, dus wat alleen in een sandbox bestaat is voor
haar onzichtbaar.
Salesforce CLI v2 (sf) op de runner. Elk commando in
deze gids is de v2-vorm, in de workflow geinstalleerd met
npm install @salesforce/cli --global. De oude sfdx CLI
is deprecated.
Een org per fase, met een duidelijke eigenaar. Minimaal een sandbox om tegen te valideren en de org waarnaar je releaset. De branching strategy verderop koppelt branches een op een aan die orgs.
Een Connected App en een integration user in elke doelorg, met digital signatures aan en de user vooraf geautoriseerd. De stappen staan verderop bij Connected App en JWT authentication.
Dit blijft het meest gebruikte model. Developers werken in sandboxes, metadata wordt naar Git gehaald en CI/CD promoot die metadata door de omgevingen.
Developer Sandbox
↓
QA
↓
UAT / Staging
↓
Production
Veel enterprise teams werken nog steeds zo, ook wanneer ze verderop in de releaseflow unlocked packages beginnen te introduceren.
Package-based development gebruikt modulaire packages in plaats van de volledige org metadata als één geheel te promoten. Dit past goed bij ISVs, AppExchange partners en enterprise teams met meerdere workstreams.
In de praktijk starten veel teams org-based en groeien ze geleidelijk naar packages voor release versioning en een schonere promotion.
Bij hele kleine teams, bijvoorbeeld twee of drie contributors, kunnen te veel omgevingen meer overhead dan waarde opleveren. Dan is een lichter proces meestal beter.
Developer Sandbox
↓
Retrieve to Git
↓
Pull Request validation
↓
Build unlocked package version
↓
Install in Staging / QA Sandbox
↓
Regression / signoff
↓
Install same package version in Production
Dit werkt wanneer het team klein is en nauw samenwerkt, releases vaak plaatsvinden, één gedeelde staging of QA omgeving genoeg is voor de laatste validatie, en het team discipline wil zonder te veel procesoverhead.
De kern is dat ook een klein team een schoon releaseproces nodig heeft. Het moet alleen niet zwaarder zijn dan het team zelf.
Naarmate teams groeien, wordt een aparte QA omgeving nuttig om geïntegreerde source changes te valideren voordat er een release candidate wordt gebouwd.
Developer Sandbox
↓
Retrieve to Git
↓
Feature branch
↓
Pull Request validation
↓
Merge to develop
↓
Deploy source to QA
↓
Create release branch
↓
Build unlocked package version
↓
Install package in UAT
↓
Regression / business signoff
↓
Install same package version in Production
Dit model is sterker voor grotere teams omdat QA en UAT een ander doel dienen:
QA valideert geïntegreerde source changes van meerdere contributors
UAT valideert het daadwerkelijke release artifact
Production ontvangt dezelfde package version die UAT doorstond
Dat geeft betere release traceability, schonere rollback-opties en minder verrassingen in production.
ISVs volgen vaak dezelfde principes, maar hun workflows zijn vanaf het begin meer package-led.
Scratch Org / Dev Org
↓
Source tracked to Git
↓
Pull Request validation
↓
Package version build
↓
Install in internal test org
↓
Install in staging / UAT
↓
Promote release artifact
Dezelfde ideeën blijven gelden: Git als source of truth, duidelijke promotion-stappen en versioned release artifacts.
Naarmate de Salesforce org groter en meer domeingedreven wordt, kan een enkel unlocked package te breed worden. Dan zijn modulaire unlocked packages meestal logischer, bijvoorbeeld voor sales, service operations, onboarding en core automation. Dit is vooral nuttig bij grote bedrijven waar meerdere teams parallel aan verschillende domeinen werken: groeit het team of de org, dan moet het DevOps-proces meegroeien om snelheid, kwaliteit, zichtbaarheid en traceability te behouden.
Kies eerst hoe werk door Git beweegt. De meest voorkomende opties zijn GitFlow en trunk-based development.
main → production history
develop → integration branch
feature/* → work item branches
release/* → release candidate branches
hotfix/* → urgent production fixes
GitFlow past goed bij teams met QA, UAT, scheduled releases en expliciete release candidate branches. Het nadeel is meer branch management en meer merge-coordinatie.
main → always releasable
feature/* → short-lived branches merged quickly
Dit werkt goed wanneer het team klein is, deployments frequent zijn, validatie en tests sterk staan, en het team liever geen langlevende branches beheert. Het nadeel is dat zwakke testdiscipline de gedeelde branch snel kan destabiliseren.
Voor veel org-based Salesforce teams is GitFlow makkelijker te adopteren omdat het het vertrouwde Dev → QA → UAT → Prod releasemodel weerspiegelt. Trunk-based development kan goed werken voor kleine, mature teams met sterke automation.
Het ene model is niet per se beter dan het andere. Belangrijker is dat je bewust een model kiest en je omgevingen, approvals en pipeline-logica ermee in lijn brengt.
Voor de meeste groeiende Salesforce teams raden we dit model aan:
Developer Sandbox
↓
Retrieve metadata to Git
↓
Feature branch
↓
Pull Request validation
↓
Merge to develop
↓
Deploy source to QA
↓
Create release branch
↓
Build unlocked package version
↓
Install package in UAT
↓
Regression testing and signoff
↓
Install same package version in Production
Waarom raden we dit model aan?
source deploy naar QA is flexibel en snel voor integratietests
de package version wordt een stabiel release artifact
UAT valideert het exacte artifact dat voor production bedoeld is
production ontvangt dezelfde geteste package version
Dit levert een proces op dat zowel praktisch als beheerst is. Het werkt ook goed met source tracking tijdens development, terwijl je verderop in de flow betere release traceability krijgt.
| Categorie | Package.xml | Source tracking + unlocked packages | Serpent workflow |
|---|---|---|---|
| Model | Manifest-driven deployment | Git-driven development met package promotion | Task-driven workflow rond Git, omgevingen en releases |
| Best voor | Hotfixes en legacy teams | Moderne teams met versioned releases | Teams die snelheid, zichtbaarheid en minder friction willen |
| Metadata selectie | Handmatig | Source tracking plus Git-selectie | Begeleide, task-based en dependency-aware selectie |
| Dependencies | Handmatig | Beter, maar discipline nodig | Ondersteund door metadata analyse en workflow logica |
| Productie traceability | Beperkt | Goed bij gebruik van package versions | Sterke zichtbaarheid over work items, releases en orgs |
| Rollback duidelijkheid | Moeilijker, vooral bij destructive changes | Beter met versioned packages | Duidelijker via release artifacts, workflow-historie en package promotion |
| Hotfix / back-promotion | Nuttig en vaak noodzakelijk | Soms nog steeds package.xml nodig voor productieherstel | Ondersteund als onderdeel van de release workflow en het zichtbaarheidsmodel |
| Operationele overhead | Laag in het begin, hoog later | Gemiddeld | Lagere doorlopende overhead omdat meer van de workflow native en zichtbaar is |
| Samenwerking | Grotendeels handmatig | Git-centric en developer-friendly | Geschikt voor developers, admins, testers en release managers |
| Wanneer we het aanraden | Specifieke edge cases en legacy processen | Aanbevolen standaard voor de meeste moderne teams | Aanbevolen wanneer je dezelfde DevOps-principes wilt met veel minder friction |
package.xml is het traditionele manifest voor metadata die je wilt ophalen of deployen.
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>AccountService</members>
<name>ApexClass</name>
</types>
<types>
<members>AccountTrigger</members>
<name>ApexTrigger</name>
</types>
<version>61.0</version>
</Package>
Voordelen:
expliciete controle over wat er wordt gedeployed
werkt in alle orgtypes, inclusief production
nuttig voor hotfixes en gerichte deploys
Nadelen:
handmatig te onderhouden
dependencies worden makkelijk gemist
weerspiegelt niet automatisch wat er in de org is gewijzigd
moeilijker te beheren naarmate de releasescope groeit
Veel teams gebruiken package.xml nog steeds, en dat is historisch verklaarbaar: lange tijd was het de meest praktische optie, zeker toen source tracking nog onvolgroeid was.
Source tracking laat Salesforce CLI vergelijken wat in een org is gewijzigd ten opzichte van lokale source. Het is vooral nuttig in scratch orgs en is de afgelopen jaren, sinds de migratie van de oude SFDX CLI naar de moderne sf CLI, flink praktischer geworden in sandbox-gebaseerde workflows.
sf project retrieve start
sf project deploy start
Voordelen:
snellere feedback tijdens development
minder handmatig manifestonderhoud
betere developer ergonomics
werkt goed in combinatie met Git-gedreven promotion
Nadelen:
production ondersteunt source tracking niet op dezelfde manier
teams hebben nog steeds een strategie voor release versioning nodig
gedeeltelijke feature-selectie kan onhandig blijven zonder de juiste tooling
Voor de meeste moderne teams raden we source tracking aan tijdens development, met unlocked packages voor release promotion. Source tracking is dagelijks een prettigere developer-ervaring dan handmatige manifesten, unlocked packages geven je verderop in de flow echte release versioning, productie traceability wordt veel duidelijker via package versions, en rollback en destructive changes zijn makkelijker te doorgronden.
package.xml blijft wel relevant in edge cases. Het echte doel is niet zuiverheid, maar het juiste gereedschap voor de juiste stap.
Bij production worden de beperkingen van source tracking belangrijk. Je kunt production niet als scratch org behandelen en Salesforce netjes laten vertellen wat er over tijd is gewijzigd.
Dat betekent dat production deployments meestal steunen op:
package.xml manifests
source directories opgebouwd vanuit Git
unlocked package versions
Dit is een van de belangrijkste redenen waarom versioned package promotion zo waardevol is: het geeft production een duidelijkere release-identiteit dan source tracking alleen daar kan bieden.
De praktische kern is simpel: production gedraagt zich niet als een source-tracked dev org, dus teams hebben een andere manier nodig om de releasestatus expliciet te maken. Unlocked packages zijn een van de beste manieren om dat te doen.
Een veelvoorkomend pijnpunt met source tracking: je developer sandbox bevat meerdere losse wijzigingen, maar je wilt maar één feature verder brengen.
Het handmatige proces ziet er meestal zo uit:
alles ophalen uit de sandbox
de gewijzigde bestanden in Git bekijken
alleen de metadata selecteren die bij de feature hoort
alleen die featurescope committen
Voorbeeld:
sf project retrieve start
git status
git add force-app/main/default/classes/PricingService.cls
git add force-app/main/default/triggers/OpportunityTrigger.trigger
git add force-app/main/default/flows/Discount_Approval.flow-meta.xml
git commit -m "Feature: pricing approval flow"
De uitdaging is dat dependencies niet altijd voor de hand liggen. Een Flow kan naar een veld verwijzen, een veld kan afhankelijk zijn van een permission set, en een wijziging in die permission set kan nodig zijn om de feature bruikbaar te maken.
Hier missen teams vaak metadata, of deployen ze juist te veel metadata om het zekere voor het onzekere te nemen.
Met Serpent wordt dit makkelijker omdat work items, metadata selection en dependency analysis in dezelfde workflow zitten.
Delta deployments pushen alleen de gewijzigde metadata. Ze zijn nuttig voor:
snel valideren van pull requests
promoten van kleine, geïsoleerde features
kortere validatietijd in zeer grote orgs
Voorbeeld met sfdx-git-delta:
sf sgd source delta \
--to HEAD \
--from origin/develop \
--output-dir delta \
--generate-delta
Valideer of deploy daarna de delta:
sf project deploy validate --source-dir delta --test-level RunLocalTests
sf project deploy start --source-dir delta
Open-source CLI suites verpakken diezelfde commando's achter een begeleid menu, een redelijk middenweg tussen ruwe scripts en een platform. Zie hoe Serpent zich verhoudt tot sfdx-hardis als je dat pad overweegt.
Full branch deployments pushen de hele metadata tree of een volledige branchstaat. Ze zijn beter bij:
het verwerken van deletions of destructive changes
het terugdraaien van commits
ervoor zorgen dat een omgeving exact een bekende branchstaat volgt
het resetten van drift in een gedeelde omgeving
het promoten van profiles of permission sets, waar het hele bestand in één stuk moet landen in plaats van als berekende diff
sf project deploy start --source-dir force-app
Delta deployments zijn geweldig zolang je vooral metadata toevoegt of wijzigt. Ze zijn minder betrouwbaar zodra de branch deletions, revert commits of bredere consistency resets bevat. Hetzelfde geldt voor types die nog in grote gedeelde bestanden leven, vooral profiles en permission sets, die niet schoon diffen en door een delta deels gemist of overschreven kunnen worden.
In de praktijk gebruikt een sterk proces allebei:
delta deploys voor snelheid
full deploys voor zekerheid wanneer het wijzigingstype dat vereist
Beide vormen duwen code omhoog. De andere richting telt net zo hard: na een hotfix rechtstreeks op productie lopen je lagere omgevingen achter totdat je back-promotion in de pipeline inricht, de stap die de meeste handgemaakte workflows overslaan.
Serpent helpt hier ook door de deploymentflow minder brittle te maken. In plaats van dat elk team deze logica handmatig in GitHub Actions naboetseert, kan de workflow aangeven wanneer gerichte beweging volstaat en wanneer een bredere promotion veiliger is.
Ook in org-based development kunnen unlocked packages het releasebeheer flink verbeteren. In plaats van een losse set metadata rechtstreeks in elke hogere omgeving te deployen, verpakken teams een logische set wijzigingen in een versioned release artifact.
Dat betekent:
duidelijke versienummers voor releases
eenvoudigere promotion van UAT naar Production
beter zicht op wat waar is gedeployed
schonere dependency-afhandeling
beter voorspelbare rollback planning
Voor een groter org-based team ziet de releaseflow er meestal zo uit:
Developer Sandbox
↓
Retrieve to Git
↓
Pull Request validation
↓
Deploy source to QA
↓
Create release branch
↓
Build unlocked package version
↓
Install in UAT
↓
Regression and approval
↓
Install same package version in Production
Voor een heel klein team kan het simpeler:
Developer Sandbox
↓
Retrieve to Git
↓
Pull Request validation
↓
Build unlocked package version
↓
Install in Staging
↓
Promote same package version to Production
sf package create --name SalesApp --package-type Unlocked --path force-app
sf package version create --package SalesApp --wait 20 --installation-key-bypass
sf package install --package 04tXXXXXXXXXXXX --target-org myUATOrg --wait 20
sf package install --package 04tXXXXXXXXXXXX --target-org myProdOrg --wait 20
Unlocked packages lossen niet elk rollbackprobleem magisch op, zeker niet zodra datawijzigingen erbij komen. Maar vergeleken met rauwe source deployment of alleen package.xml-promotion maken ze rollback makkelijker te begrijpen, omdat:
de release een versioned artifact is
je precies weet welke package version de wijziging introduceerde
je duidelijker kunt vergelijken wat er tussen versions is gewijzigd
destructive changes makkelijker te volgen zijn binnen de releasehistorie
Dit weegt nog zwaarder in production, waar source tracking je niet dezelfde zichtbaarheid geeft als in development-omgevingen.
GitHub Actions is geschikt voor teams die de mechanics willen begrijpen en hun eigen CI/CD foundation willen bouwen. Het integreert direct met je repository, en workflows staan als YAML-bestanden naast de code.
feature/* → developer work item branches
develop → source-integrated branch deployed to QA
release/* → release candidate branches used for package version build
main → production history
hotfix/* → urgent production fixes
Het voorbeeld hieronder laat een praktische end-to-end opzet zien met:
PR-validatie richting develop
automatische source deploy naar QA bij merge naar develop
package version build op release/*
package install naar UAT
handmatige promotion van dezelfde package version naar Production
name: Salesforce End-to-End CI/CD
on:
pull_request:
branches:
- develop
push:
branches:
- develop
- 'release/*'
workflow_dispatch:
inputs:
package_version_id:
description: 'Unlocked package version id to promote to Production'
required: false
type: string
env:
PROJECT_DIR: force-app
DELTA_DIR: delta
QA_ALIAS: qa
UAT_ALIAS: uat
PROD_ALIAS: prod
PACKAGE_NAME: SalesApp
jobs:
pr-validate:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Salesforce CLI
run: npm install @salesforce/cli --global
- name: Install plugins
run: |
sf plugins install sfdx-git-delta
sf plugins install @salesforce/plugin-packaging
- name: Create JWT key file
run: echo "${{ secrets.SF_JWT_KEY_QA }}" > server.key
- name: Authenticate to QA validation org
run: |
sf org login jwt \
--client-id ${{ secrets.SF_CLIENT_ID_QA }} \
--jwt-key-file server.key \
--username ${{ secrets.SF_USERNAME_QA }} \
--instance-url ${{ secrets.SF_INSTANCE_URL_QA }} \
--alias $QA_ALIAS
- name: Generate delta package
run: |
sf sgd source delta \
--to HEAD \
--from origin/develop \
--output-dir $DELTA_DIR \
--generate-delta
- name: Show generated delta
run: |
echo "Delta contents:"
find $DELTA_DIR || true
- name: Run PMD scan
run: |
pmd -d $PROJECT_DIR -R category/apex/design.xml -f text || true
- name: Run ESLint
run: |
if [ -f package.json ]; then
npm ci
npm run lint || true
fi
- name: Validate delta deployment against QA
run: |
if [ -d "$DELTA_DIR" ]; then
sf project deploy validate \
--source-dir $DELTA_DIR \
--target-org $QA_ALIAS \
--test-level RunLocalTests
else
echo "No delta generated, validating full source instead."
sf project deploy validate \
--source-dir $PROJECT_DIR \
--target-org $QA_ALIAS \
--test-level RunLocalTests
fi
deploy-to-qa:
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Salesforce CLI
run: npm install @salesforce/cli --global
- name: Install plugins
run: |
sf plugins install sfdx-git-delta
- name: Create JWT key file
run: echo "${{ secrets.SF_JWT_KEY_QA }}" > server.key
- name: Authenticate to QA
run: |
sf org login jwt \
--client-id ${{ secrets.SF_CLIENT_ID_QA }} \
--jwt-key-file server.key \
--username ${{ secrets.SF_USERNAME_QA }} \
--instance-url ${{ secrets.SF_INSTANCE_URL_QA }} \
--alias $QA_ALIAS
- name: Generate delta for QA deploy
run: |
sf sgd source delta \
--to HEAD \
--from HEAD^ \
--output-dir $DELTA_DIR \
--generate-delta || true
- name: Deploy to QA
run: |
if [ -d "$DELTA_DIR" ] && [ "$(find $DELTA_DIR -type f | wc -l)" -gt 0 ]; then
sf project deploy start \
--source-dir $DELTA_DIR \
--target-org $QA_ALIAS \
--test-level RunLocalTests
else
echo "Falling back to full branch deploy"
sf project deploy start \
--source-dir $PROJECT_DIR \
--target-org $QA_ALIAS \
--test-level RunLocalTests
fi
build-package-and-install-uat:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release/')
runs-on: ubuntu-latest
outputs:
package_version_id: ${{ steps.capture_version.outputs.package_version_id }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Salesforce CLI
run: npm install @salesforce/cli --global
- name: Install packaging plugin
run: sf plugins install @salesforce/plugin-packaging
- name: Create Dev Hub JWT key file
run: echo "${{ secrets.SF_JWT_KEY_DEVHUB }}" > server.key
- name: Authenticate to Dev Hub
run: |
sf org login jwt \
--client-id ${{ secrets.SF_CLIENT_ID_DEVHUB }} \
--jwt-key-file server.key \
--username ${{ secrets.SF_USERNAME_DEVHUB }} \
--instance-url ${{ secrets.SF_INSTANCE_URL_DEVHUB }} \
--alias devhub
- name: Create package version
run: |
sf package version create \
--package $PACKAGE_NAME \
--target-dev-hub devhub \
--wait 30 \
--installation-key-bypass \
--code-coverage
- name: Capture latest package version id
id: capture_version
run: |
VERSION_ID=$(sf package version list --packages $PACKAGE_NAME --target-dev-hub devhub --json | jq -r '.result[0].SubscriberPackageVersionId')
echo "package_version_id=$VERSION_ID" >> $GITHUB_OUTPUT
echo "Latest package version id: $VERSION_ID"
- name: Create UAT JWT key file
run: echo "${{ secrets.SF_JWT_KEY_UAT }}" > server.key
- name: Authenticate to UAT
run: |
sf org login jwt \
--client-id ${{ secrets.SF_CLIENT_ID_UAT }} \
--jwt-key-file server.key \
--username ${{ secrets.SF_USERNAME_UAT }} \
--instance-url ${{ secrets.SF_INSTANCE_URL_UAT }} \
--alias $UAT_ALIAS
- name: Install package in UAT
run: |
sf package install \
--package ${{ steps.capture_version.outputs.package_version_id }} \
--target-org $UAT_ALIAS \
--wait 30 \
--publish-wait 10 \
--no-prompt
promote-to-production:
if: github.event_name == 'workflow_dispatch' && github.event.inputs.package_version_id != ''
runs-on: ubuntu-latest
environment: production
steps:
- name: Install Salesforce CLI
run: npm install @salesforce/cli --global
- name: Create Production JWT key file
run: echo "${{ secrets.SF_JWT_KEY_PROD }}" > server.key
- name: Authenticate to Production
run: |
sf org login jwt \
--client-id ${{ secrets.SF_CLIENT_ID_PROD }} \
--jwt-key-file server.key \
--username ${{ secrets.SF_USERNAME_PROD }} \
--instance-url ${{ secrets.SF_INSTANCE_URL_PROD }} \
--alias $PROD_ALIAS
- name: Install approved package in Production
run: |
sf package install \
--package ${{ github.event.inputs.package_version_id }} \
--target-org $PROD_ALIAS \
--wait 30 \
--publish-wait 10 \
--no-prompt
Dit is niet het enige geldige GitHub Actions ontwerp, maar het toont de aanbevolen scheiding tussen source-based validatie in lagere omgevingen en package-based promotion naar UAT en Production.
Een CI/CD pipeline is maar een deel van een werkend Salesforce DevOps proces. Wat vaak het verschil maakt tussen slagen en falen is hoe teams omgaan met de operationele details rond development, testen, promotion en releasecoördinatie.
Een uitdaging in Salesforce development is dat sandboxes vaak meerdere wijzigingen tegelijk opstapelen. Een developer werkt misschien aan twee of drie features, maar slechts één is klaar voor promotion.
Als je simpelweg alle metadata ophaalt en doorzet, riskeer je onaf werk te promoten.
Het veiligere patroon is:
Developer Sandbox
↓
Retrieve all metadata locally
↓
Select only the components belonging to the feature
↓
Commit those components to a feature branch
↓
Submit a pull request for validation
Voorbeeldworkflow:
sf project retrieve start
git checkout -b feature/pricing-update
git add force-app/main/default/classes/PricingService.cls
git add force-app/main/default/triggers/PricingTrigger.trigger
git add force-app/main/default/lwc/pricingWidget
git commit -m "Feature: pricing update"
Zo weerspiegelt de repository één logische feature in plaats van een mix van niet gerelateerde wijzigingen.
Het lastige is dat Salesforce metadata vaak onderling verbonden is. Een Flow kan afhankelijk zijn van:
custom fields
Apex classes
permission sets
record types
Het missen van zo'n dependency kan de deploymentvalidatie breken.
Veel teams lossen dit op via ervaring en handmatige checks. Platforms zoals Serpent helpen door metadatarelaties te analyseren en dependencies automatisch te herkennen, zodat de juiste set componenten samen meebeweegt.
Recente verbeteringen in developer tooling hebben AI-ondersteuning voor dependency-analyse geintroduceerd.
In de praktijk betekent dit:
een featurebeschrijving kan worden geanalyseerd
gerelateerde metadatacomponenten kunnen worden gedetecteerd
dependency graphs kunnen worden geëvalueerd
de meest waarschijnlijke metadatascope kan worden voorgesteld
Serpent bevat AI-agents die zijn getraind op Salesforce metadatapatronen en die helpen de logische set componenten voor een feature te bepalen, wat het risico op onvolledige deployments verkleint.
Dit haalt de controle niet bij de developer weg. Het maakt het selectieproces alleen sneller en veiliger.
Een ander veelvoorkomend probleem in Salesforce omgevingen is sandbox drift.
Dit gebeurt wanneer iemand een wijziging rechtstreeks doorvoert in een gedeelde omgeving zoals QA of UAT, zonder dat die ooit in de Git repository terechtkomt.
Na verloop van tijd raakt de omgeving los van source control.
Om drift te detecteren, doen teams vaak periodieke retrieve-vergelijkingen:
sf project retrieve start
git diff
Verschijnen er onverwachte verschillen, dan moeten die meteen worden opgelost door ze naar Git te committen of te verwijderen.
Laat je drift opstapelen, dan worden latere deployments onvoorspelbaar.
Elke omgeving moet een duidelijk doel hebben.
| Omgeving | Doel |
|---|---|
| Developer Sandbox | Feature development en vroege tests |
| QA | Geïntegreerde tests over meerdere features |
| UAT | Business validatie van release candidates |
| Production | Live systeem |
Problemen ontstaan vaak wanneer omgevingen hun doel verliezen. Wordt QA zelf weer een development sandbox, dan werkt het niet langer als betrouwbaar integratiepunt.
Een robuust CI/CD proces bevat automated tests op meerdere niveaus.
unit tests voor Apex
component tests voor LWC
integratietests voor cross-feature workflows
PR-validatiepipelines draaien vaak gerichte tests voor snelheid, terwijl releasepipelines bredere testsuites draaien.
Voorbeeld PR-validatie:
sf project deploy validate \
--source-dir delta \
--test-level RunSpecifiedTests
Voorbeeld releasevalidatie:
sf project deploy validate \
--source-dir force-app \
--test-level RunLocalTests
Het doel is problemen vroeg te vangen zonder de developer-feedbackcyclus onnodig te vertragen. Welke suite op welke gate draait, en waar UI-coverage past, is een onderwerp op zich: hoe je een Salesforce test automation pipeline in CI bouwt loopt de lagen in volgorde langs.
Een van de meest onderschatte DevOps-uitdagingen is simpelweg weten wat er in een release zit.
Vragen die teams vaak stellen:
Welke features zitten in de volgende release?
Welke componenten zijn gisteren naar QA gedeployed?
Welke package version staat momenteel in production?
Zonder gestructureerde workflow vraagt het beantwoorden van die vragen vaak zoeken door Git-historie, pipelinelogs en handmatige release notes.
Serpent pakt dit aan door deployments te koppelen aan work items, waardoor releases makkelijker te begrijpen zijn over omgevingen heen.
Ook developerproductiviteit telt.
Veel developers werken het liefst in VS Code met de Salesforce-extensies en CLI-tools.
DevOps platforms moeten zich aanpassen aan die workflow, in plaats van developers naar volledig aparte interfaces te dwingen.
Serpent heeft een VS Code extension waarmee developers:
metadata kunnen vergelijken
deployments kunnen valideren
releasetaken kunnen beheren
wijzigingen kunnen deployen
Dit houdt DevOps-operaties dicht bij de development-omgeving.
De pipelinevoorbeelden hierboven gaan ervan uit dat je GitHub Actions runner headless kan authenticeren bij Salesforce. Het standaardpatroon is JWT-based auth via een Connected App. Hier is de praktische opzet, end to end.
Ga in Salesforce Setup naar App Manager → New Connected App.
Zet OAuth aan en gebruik als callback URL
http://localhost:1717/OauthRedirect.
Selecteer de scopes Manage user data via APIs (api) en Perform requests at any time (refresh_token, offline_access).
Zet Use digital signatures aan en upload je certificaat (hieronder gegenereerd).
openssl genrsa -out server.key 2048
openssl req -new -x509 -days 1825 -key server.key -out server.crt
Upload server.crt naar de Connected App. Bewaar
server.key veilig - die gaat in GitHub Secrets, nooit in de repo.
Ga in je GitHub repository naar Settings → Secrets and variables → Actions en voeg toe:
De workflow hierboven authenticeert bij vier losse orgs - QA, de Dev Hub, UAT en
Production - dus heeft elk secret een kopie per omgeving nodig, met het achtervoegsel
_QA, _DEVHUB, _UAT of _PROD
(bijvoorbeeld SF_CLIENT_ID_QA en SF_CLIENT_ID_DEVHUB).
Herhaal de vier secrets hieronder voor elk achtervoegsel, wijzend naar de Connected
App en org van die specifieke omgeving:
SF_CLIENT_ID_<ENV> - de Consumer Key van de Connected App
van die omgeving.
SF_USERNAME_<ENV> - de Salesforce gebruikersnaam van die
omgeving, bijvoorbeeld [email protected].
SF_JWT_KEY_<ENV> - de inhoud van het
server.key bestand van die omgeving.
SF_INSTANCE_URL_<ENV> - https://test.salesforce.com
voor sandboxes (QA, UAT, Dev Hub) of https://login.salesforce.com
voor production.
De meeste CI/CD-fouten vallen in een kleine set terugkerende problemen. De tabel hieronder behandelt de fouten die we het vaakst zien.
| Error | Waarschijnlijke oorzaak | Fix |
|---|---|---|
INVALID_LOGIN |
JWT key of client ID klopt niet | Controleer secrets en trailing newlines. |
INSUFFICIENT_ACCESS |
Connected App permissies | Controleer scopes en activatie voor de integration user. |
| Tests failing on deploy | Code coverage onder 75 procent | Draai sf apex run test lokaal en verhoog coverage. |
| Metadata not deploying | SFDX-projectstructuur klopt niet |
Controleer of sfdx-project.json de juiste
packageDirectories heeft.
|
| Job loopt vast tijdens deploy | Langlopende validatie of full-org tests |
Gebruik --test-level RunLocalTests bij PR-validatie. Reserveer
RunAllTestsInOrg voor de productiejob.
|
Hoe authenticeert GitHub Actions bij een Salesforce-org?
Via een Connected App met de JWT-flow. Je maakt de app aan in App Manager, uploadt een certificaat en bewaart de private key plus de consumer key in GitHub Secrets, waarna de workflow daarmee inlogt voor elke deploystap.
Waarom faalt mijn pipeline met INVALID_LOGIN?
De JWT-key of de client ID komt niet overeen met wat de Connected App verwacht. Controleer je secrets teken voor teken en let op een trailing newline aan het eind van de private key.
Waarom falen mijn tests tijdens de deploy?
Meestal ligt de code coverage onder 75 procent. Draai sf apex run test
lokaal om te zien welke klassen dekking missen, en gebruik bij PR-validatie
RunLocalTests zodat de job snel blijft. Welke suite op welke gate hoort,
leggen we uit in
de gids over test automation in CI.
Kies je delta deployments of full deployments?
Delta deployments pushen alleen wat er is gewijzigd en zijn ideaal voor het valideren van pull requests en kleine features in grote orgs. Full deployments zijn veiliger bij verwijderingen, reverts en als een omgeving exact een branchstaat moet volgen.
Heb ik sfdx-git-delta nodig?
Nee, maar het is de moeite waard zodra deploys traag worden. Combineer het met een full deploy voor verwijderingen, zodat destructive changes worden afgehandeld en niet stilletjes overgeslagen.
Moet ik sfdx of sf gebruiken voor een Salesforce GitHub Actions-pijplijn?
Gebruik sf, de moderne Salesforce CLI, want de oude sfdx CLI
die hij verving is deprecated en hoort niet de basis van een nieuwe pijplijn te zijn.
Een bestaande pijplijn overzetten is vooral een kwestie van commandonamen:
sfdx force:source:deploy wordt sf project deploy start,
sfdx force:apex:test:run wordt sf apex run test, en plugins
installeer je opnieuw via sf plugins install.
Waarom valideren bij de pull request en bij merge opnieuw deployen?
De pull request-job bewijst dat de branch compileert en zijn tests haalt tegen de doelorg, zonder er iets naartoe te schrijven. De merge-job wijzigt de org wel, en draait tegen de merge-commit, niet tegen de boom die de pull request valideerde.
Salesforce DevOps draait niet om één tool of één perfecte pipeline. Het gaat om een betrouwbaar proces waarin Git de source of truth is, wijzigingen automatisch worden gevalideerd, releases versioned zijn en omgevingen consistent blijven.
Kleine teams beginnen vaak met eenvoudige pipelines en één staging omgeving. Grotere organisaties hebben gestructureerde omgevingen, versioned releases en modulaire packages nodig.
GitHub Actions helpt teams de onderliggende automation te begrijpen. Naarmate Salesforce omgevingen complexer worden, helpen tools zoals Serpent om snelheid te behouden zonder controle, zichtbaarheid en release confidence te verliezen.
Vrijblijvend.