How to Use Jenkins with Azure DevOps for CI/CD

How to Use Jenkins with Azure DevOps for CI/CD

I once worked on a team that was fully committed to Azure Boards for planning and Azure Repos for source control, but wanted to keep Jenkins for the actual build and deploy orchestration since that’s where all our existing pipeline logic already lived. That combination turns out to work really well once you understand how the two systems talk to each other. This guide covers connecting Jenkins to Azure DevOps for source control triggers, build status reporting, and deployment to Azure.

Why Combine Jenkins and Azure DevOps

Azure DevOps offers its own native pipelines (Azure Pipelines), so pairing it with Jenkins makes sense when you already have Jenkins infrastructure and expertise, need Jenkins-specific plugins not available in Azure Pipelines, or run a hybrid environment where some services deploy via Jenkins and others via Azure Pipelines, with Azure Repos as the shared source of truth.

Jenkins Architecture in This Setup

Prerequisites

Step 1: Install Required Plugins

Step 2: Create a Service Principal for Azure Authentication

az login
az ad sp create-for-rbac --name "jenkins-deploy-sp" \
  --role Contributor \
  --scopes /subscriptions/<subscription-id>/resourceGroups/<resource-group>

This returns an appId, password, and tenant — store these in Jenkins Credentials as an “Azure Service Principal” credential type (provided by the Azure Credentials Plugin).

Step 3: Connect Jenkins to Azure Repos

  1. In Azure DevOps, go to Project Settings > Repositories and generate a Personal Access Token (PAT) with Code (Read) scope, or set up SSH keys.
  2. In Jenkins, create a new Pipeline job (or multibranch pipeline) and set the repository URL to your Azure Repos Git URL: https://dev.azure.com/{org}/{project}/_git/{repo}.
  3. Add the PAT as a Jenkins “Username with password” credential (username can be anything, password is the PAT).

Step 4: Set Up Webhook Triggers from Azure Repos

Azure Repos supports Service Hooks to notify external systems on push events:

  1. In Azure DevOps: Project Settings > Service Hooks > Create Subscription.
  2. Choose Web Hooks as the service, and Code pushed as the trigger event.
  3. Set the URL to https://your-jenkins-url/git/notifyCommit?url=<repo-url> or, if using the generic webhook trigger plugin, a custom endpoint that parses the Azure DevOps payload.

Alternatively, for more reliable integration, use the Azure Pipelines built-in ability to call Jenkins as an external build step — some teams run a minimal Azure Pipeline purely to trigger a Jenkins job via the Jenkins REST API:

# azure-pipelines.yml — triggers Jenkins remotely
trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - script: |
      curl -X POST "https://your-jenkins-url/job/myapp-pipeline/build" \
        --user "$(JENKINS_USER):$(JENKINS_TOKEN)" \
        --data-urlencode json='{"parameter": [{"name":"COMMIT_SHA","value":"$(Build.SourceVersion)"}]}'
    displayName: 'Trigger Jenkins Pipeline'

Step 5: Write the Jenkinsfile

pipeline {
    agent any

    environment {
        AZURE_RESOURCE_GROUP = 'myapp-rg'
        AZURE_WEBAPP_NAME = 'myapp-webapp'
    }

    stages {
        stage('Checkout') {
            steps {
                git branch: 'main',
                    credentialsId: 'azure-repos-pat',
                    url: 'https://dev.azure.com/myorg/myproject/_git/myapp'
            }
        }

        stage('Install Dependencies') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Run Tests') {
            steps {
                sh 'npm test'
            }
        }

        stage('Build') {
            steps {
                sh 'npm run build'
                sh 'zip -r app.zip dist/'
            }
        }

        stage('Azure Login') {
            steps {
                withCredentials([azureServicePrincipal('azure-deploy-sp')]) {
                    sh '''
                        az login --service-principal \
                          -u $AZURE_CLIENT_ID \
                          -p $AZURE_CLIENT_SECRET \
                          --tenant $AZURE_TENANT_ID
                    '''
                }
            }
        }

        stage('Deploy to Azure Web App') {
            steps {
                sh '''
                    az webapp deploy \
                      --resource-group $AZURE_RESOURCE_GROUP \
                      --name $AZURE_WEBAPP_NAME \
                      --src-path app.zip \
                      --type zip
                '''
            }
        }
    }

    post {
        success {
            azureUpdatePullRequest(status: 'succeeded')
        }
        failure {
            azureUpdatePullRequest(status: 'failed')
        }
    }
}

azureServicePrincipal (provided by the Azure Credentials Plugin) exposes AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID as environment variables scoped to the block.

Step 6: Reporting Build Status Back to Azure DevOps

For pull requests in Azure Repos, use the Azure DevOps REST API to post a status check so reviewers see Jenkins results directly on the PR:

stage('Report Status to Azure DevOps') {
    steps {
        withCredentials([string(credentialsId: 'azure-devops-pat', variable: 'ADO_PAT')]) {
            sh '''
                curl -X POST \
                  "https://dev.azure.com/myorg/myproject/_apis/git/repositories/myapp/pullRequests/$PR_ID/statuses?api-version=7.0" \
                  -H "Content-Type: application/json" \
                  -u ":$ADO_PAT" \
                  -d '{
                        "state": "succeeded",
                        "description": "Jenkins build passed",
                        "context": {"name": "jenkins-ci", "genre": "continuous-integration"}
                      }'
            '''
        }
    }
}

Deploying to Different Azure Targets

The same Jenkinsfile pattern extends to other Azure services with minor swaps:

Real-World Workflow

  1. A developer pushes to a feature branch in Azure Repos.
  2. A service hook (or lightweight Azure Pipeline trigger) notifies Jenkins, which runs tests and reports status back to the PR.
  3. On merge to main, Jenkins builds, authenticates to Azure via Service Principal, and deploys to a staging Azure Web App slot.
  4. A smoke test hits the staging slot; if healthy, Jenkins swaps the staging and production deployment slots using az webapp deployment slot swap — giving near-zero-downtime cutover.
  5. Work items linked to the commit in Azure Boards automatically transition state via Azure DevOps’ built-in commit-linking, independent of Jenkins.
stage('Swap Deployment Slots') {
    steps {
        input message: 'Swap staging into production?'
        sh '''
            az webapp deployment slot swap \
              --resource-group $AZURE_RESOURCE_GROUP \
              --name $AZURE_WEBAPP_NAME \
              --slot staging \
              --target-slot production
        '''
    }
}

Security Best Practices

Troubleshooting

Linking Jenkins Builds to Azure Boards Work Items

Traceability between a deployment and the work item it resolves is one of the more useful things this integration enables. If commit messages reference a work item ID (Azure Boards’ default convention is #123 or AB#123), Jenkins can parse that and update the work item’s state via the REST API once a deployment succeeds:

stage('Update Linked Work Items') {
    steps {
        withCredentials([string(credentialsId: 'azure-devops-pat', variable: 'ADO_PAT')]) {
            script {
                def commitMsg = sh(script: 'git log -1 --pretty=%B', returnStdout: true).trim()
                def matcher = commitMsg =~ /AB#(\d+)/
                if (matcher.find()) {
                    def workItemId = matcher.group(1)
                    sh """
                        curl -X PATCH \
                          "https://dev.azure.com/myorg/myproject/_apis/wit/workitems/${workItemId}?api-version=7.0" \
                          -H "Content-Type: application/json-patch+json" \
                          -u ":\$ADO_PAT" \
                          -d '[{"op":"add","path":"/fields/System.State","value":"Deployed"}]'
                    """
                }
            }
        }
    }
}

This closes the loop between planning (Azure Boards) and delivery (Jenkins) without requiring the team to manually update ticket status after every release.

Handling Azure DevOps Variable Groups and Key Vault

If your team already stores shared configuration in Azure DevOps Variable Groups, Jenkins can pull those values via the REST API rather than duplicating configuration in two places:

stage('Fetch Variable Group') {
    steps {
        withCredentials([string(credentialsId: 'azure-devops-pat', variable: 'ADO_PAT')]) {
            sh '''
                curl -s -u ":$ADO_PAT" \
                  "https://dev.azure.com/myorg/myproject/_apis/distributedtask/variablegroups/1?api-version=7.0" \
                  | jq -r '.variables' > variables.json
            '''
        }
    }
}

For secrets specifically, prefer Azure Key Vault directly over Variable Groups — the Azure Key Vault Plugin for Jenkins can fetch secrets at pipeline runtime, keeping a single source of truth shared across both Azure Pipelines and Jenkins without copy-pasting values between systems.

FAQs

Do I need Azure Pipelines at all if I’m using Jenkins? Not necessarily — some teams use Azure DevOps purely for Repos and Boards, keeping all pipeline logic in Jenkins. Others use a thin Azure Pipeline as a trigger relay, as shown above.

Can Jenkins read and update Azure Boards work items directly? Yes, via the Azure DevOps REST API using a PAT, though this typically requires custom pipeline scripting since there’s no dedicated plugin for work item updates as robust as the source control integration.

Is Azure Repos Git functionally different from GitHub for Jenkins purposes? No — it’s standard Git under the hood; the differences are in authentication (PAT vs GitHub token) and webhook/service hook configuration syntax.

What’s the advantage of deployment slots over a direct deploy? Slots let you deploy and warm up the new version before it receives production traffic, and the swap operation is nearly instant with an automatic rollback path (swap back) if something’s wrong.

Summary

Jenkins and Azure DevOps integrate cleanly once you set up the right authentication pieces: a Service Principal for Azure resource access, a PAT for Azure Repos/API interaction, and either a service hook or a thin Azure Pipeline trigger to kick off builds. From there, deploying to Azure Web Apps, AKS, or Functions follows the same checkout-test-build-deploy pattern as any other Jenkins pipeline.

References

Exit mobile version