I have been iterating on the ADF CI/CD pattern since 2018 when git integration shipped. I've rebuilt it for five different clients, hit the same problems each time, and landed on a pattern that I now consider stable. Here it is -- the complete picture, including the parts Microsoft's documentation glosses over.
The Architecture
Three environments: Dev, Test, Prod. Each is a separate ADF instance with its own linked services pointing to environment-appropriate resources (different connection strings, different storage accounts, different SQL servers).
Git integration is configured on Dev only. Developers work in Dev ADF Studio, push feature branches to Azure Repos (or GitHub), and open PRs against the collaboration branch (I use main). PR reviews happen in Git. Merges go to main.
When a developer is ready to deploy to Test/Prod, they click Publish in Dev ADF Studio. This generates ARM templates and writes them to the adf_publish branch. An Azure DevOps pipeline triggers on changes to adf_publish and runs the multi-stage deployment.
Test and Prod ADF instances are not connected to git -- they receive ARM template deployments only. This is intentional. You don't want someone hand-editing pipelines in Prod ADF Studio and having those changes overwritten on the next deployment.
The ADO Pipeline Structure
trigger:
branches:
include:
- adf_publish
stages:
- stage: DeployTest
jobs:
- job: Deploy
steps:
- task: AzurePowerShell@5
displayName: Pre-deployment (stop triggers)
inputs:
azureSubscription: service-connection-test
ScriptPath: $(Build.Repository.LocalPath)/adf-cicd/pre-deploy.ps1
- task: AzureResourceManagerTemplateDeployment@3
displayName: Deploy ARM Template
inputs:
deploymentScope: Resource Group
azureResourceManagerConnection: service-connection-test
resourceGroupName: $(testResourceGroup)
csmFile: adf_publish/$(devAdfName)/ARMTemplateForFactory.json
csmParametersFile: adf-cicd/parameters/test.parameters.json
- task: AzurePowerShell@5
displayName: Post-deployment (restart triggers)
inputs:
azureSubscription: service-connection-test
ScriptPath: $(Build.Repository.LocalPath)/adf-cicd/post-deploy.ps1
- stage: DeployProd
dependsOn: DeployTest
condition: succeeded()
The Pre-Deployment Script
Triggers must be stopped before ARM deployment. If you deploy while triggers are running, the deployment fails or leaves triggers in an inconsistent state. This is not documented prominently enough.
param(
[string]$ResourceGroupName,
[string]$DataFactoryName
)
$triggers = Get-AzDataFactoryV2Trigger `
-ResourceGroupName $ResourceGroupName `
-DataFactoryName $DataFactoryName
foreach ($trigger in $triggers) {
if ($trigger.RuntimeState -eq "Started") {
Write-Host "Stopping trigger: $($trigger.Name)"
Stop-AzDataFactoryV2Trigger `
-ResourceGroupName $ResourceGroupName `
-DataFactoryName $DataFactoryName `
-Name $trigger.Name `
-Force
}
}
Write-Host "All triggers stopped."
The Post-Deployment Script
After ARM deployment, restart only the triggers that should be active in this environment.
param(
[string]$ResourceGroupName,
[string]$DataFactoryName,
[string[]]$TriggerNames
)
foreach ($triggerName in $TriggerNames) {
Write-Host "Starting trigger: $triggerName"
Start-AzDataFactoryV2Trigger `
-ResourceGroupName $ResourceGroupName `
-DataFactoryName $DataFactoryName `
-Name $triggerName `
-Force
}
Write-Host "Triggers started."
The Environment Parameter File Pattern
The ARM template generated by ADF Publish includes a parameters section for every linked service connection string, every global parameter, and every integration runtime reference. The environment parameter file overrides these with environment-specific values.
Keep this file in the repo under adf-cicd/parameters/, one file per environment. Never hardcode connection strings in the ADF UI -- always use global parameters that get overridden by the environment parameter file.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"AzureSqlDatabase_connectionString": {
"value": "Server=tcp:prod-sql.database.windows.net;Database=prod-db;..."
}
}
}
The One Remaining Rough Edge
The Publish button is still manual. When your team merges a PR to main, the collaboration branch is up to date, but adf_publish is not. Someone has to go to Dev ADF Studio, verify they're on main, and click Publish. There is no way to automate this step with built-in ADF functionality.
The community workaround is the microsoft/azure-data-factory-utilities npm package, which exposes a programmatic publish API. I've used it in one engagement and it works, but it's community tooling, not a Microsoft-supported feature. I'll write a dedicated post on automated publish when I have more production miles on it.
Questions about wiring this up for your ADF deployment? I'm here to help.