Introduction to CI/CD
A CI/CD pipeline is the backbone of a high-performing engineering team. Without one, deployments are manual, error-prone, and slow. With one, every code change is automatically tested, built, and delivered to production — or stopped at the gate if something breaks.
CI stands for Continuous Integration: the practice of automatically building and testing code every time a developer pushes a change. CD stands for Continuous Delivery (or Deployment): automatically delivering that tested code to one or more environments.
“High-performing engineering teams deploy to production multiple times per day. The pipeline is what makes that safe — not heroics, not careful humans, but automated gates that catch problems before they reach users.”
In the Azure ecosystem, two tools dominate: Azure DevOps Pipelines and GitHub Actions. They are not competitors — they are complementary. Many teams use both, letting each do what it does best. This article explains how to combine them into a complete, production-grade pipeline.
What a complete pipeline looks like
- Developer pushes code to a feature branch on GitHub.
- GitHub Actions triggers: runs unit tests, builds a Docker image, pushes it to Azure Container Registry.
- A pull request is opened — the CI checks must pass before merge is allowed.
- Code is merged to main. GitHub Actions tags the image with the commit SHA.
- Azure DevOps detects the new image in ACR and triggers a release pipeline.
- The release pipeline deploys to staging, runs smoke tests, waits for manual approval, then deploys to production.
Azure DevOps vs GitHub Actions
Both tools can build, test, and deploy code. The question is not which is better — it is which is better for each part of the job.
GitHub Actions strengths
- Native to GitHub — triggers on every GitHub event: push, PR, issue comment, release, schedule.
- Massive marketplace of pre-built actions (checkout, Docker build, Azure login, etc.).
- YAML-first, simple syntax — easy to version alongside application code.
- Free for public repositories; generous free tier for private repos.
- Best for CI: building, testing, linting, security scanning, and image publishing.
Azure DevOps strengths
- Mature release pipeline UI with approval gates, deployment stages, and rollback controls.
- Deep integration with Azure: service connections, Azure Key Vault variable groups, environments with checks.
- Boards, Repos, Test Plans, Artifacts — a full ALM suite if you need it.
- Better audit trail for enterprise compliance — who approved what, when, and why.
- Best for CD: controlled multi-stage releases with approvals and environment governance.
The winning combination for Azure workloads: use GitHub Actions for the CI phase (test, build, push image to ACR) and Azure DevOps for the CD phase (pull from ACR, deploy to AKS or App Service with approval gates). Each tool does what it is designed for.
Building the Pipeline
The pipeline below covers the full path from code push to production deployment. It uses GitHub Actions for CI and Azure DevOps for the release.
GitHub Actions: CI workflow
This workflow runs on every push to main and on pull requests. It builds and pushes a Docker image to Azure Container Registry tagged with the Git commit SHA — a stable, immutable reference for the release pipeline to consume.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
ACR_NAME: myregistry.azurecr.io
IMAGE_NAME: myapp
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run unit tests
run: dotnet test --configuration Release
- name: Log in to Azure Container Registry
uses: azure/docker-login@v1
with:
login-server: ${{ env.ACR_NAME }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push Docker image
run: |
docker build -t $ACR_NAME/$IMAGE_NAME:${{ github.sha }} .
docker push $ACR_NAME/$IMAGE_NAME:${{ github.sha }}Azure DevOps: multi-stage release pipeline
The Azure DevOps pipeline picks up the image published by GitHub Actions and deploys it through staging and production. The approval gate between stages ensures a human confirms before production is touched.
# azure-pipelines.yml
trigger: none # triggered by ACR image push, not code push
resources:
containers:
- container: app
type: ACR
azureSubscription: my-azure-service-connection
resourceGroup: my-rg
registry: myregistry
repository: myapp
trigger:
tags:
include: ['*']
stages:
- stage: Staging
jobs:
- deployment: DeployStaging
environment: staging
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@1
inputs:
action: deploy
kubernetesServiceConnection: aks-staging
manifests: k8s/deployment.yaml
containers: myregistry.azurecr.io/myapp:$(resources.container.app.tag)
- stage: Production
dependsOn: Staging
jobs:
- deployment: DeployProduction
environment: production # has approval check configured
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@1
inputs:
action: deploy
kubernetesServiceConnection: aks-production
manifests: k8s/deployment.yaml
containers: myregistry.azurecr.io/myapp:$(resources.container.app.tag)In Azure DevOps, go to Pipelines → Environments → production → Approvals and checks to add required reviewers. The pipeline will pause at the Production stage until an approver confirms, giving your team a final human gate before every production release.
Secrets Management and Rollback Strategies
Two things break most pipelines in production: secrets leaking into logs or source control, and no clear path to roll back a bad deployment. Both are preventable.
Secrets with Azure Key Vault
Never store secrets in pipeline YAML files or repository settings beyond what is absolutely necessary. Use Azure Key Vault as the single source of truth. In Azure DevOps, link a Key Vault to a variable group — the pipeline reads secrets at runtime without any human ever seeing them.
# In azure-pipelines.yml — reference a Key Vault-linked variable group
variables:
- group: production-secrets # linked to Azure Key Vault
steps:
- script: echo "Deploying with connection string $(DatabaseConnectionString)"
# DatabaseConnectionString is fetched from Key Vault at runtime
# It is masked in logs automaticallyIn GitHub Actions, use the azure/get-keyvault-secrets action to pull secrets from Key Vault into the workflow environment. Secrets fetched this way are automatically masked in logs.
- name: Get secrets from Key Vault
uses: Azure/get-keyvault-secrets@v1
with:
keyvault: my-keyvault
secrets: 'DatabaseConnectionString, ApiKey'
id: keyvaultSecrets
- name: Use secret
run: echo ${{ steps.keyvaultSecrets.outputs.DatabaseConnectionString }}Rollback with deployment slots (App Service)
Azure App Service deployment slots give you zero-downtime deployments with an instant rollback path. Deploy to a staging slot, run smoke tests, then swap slots to promote to production. If something goes wrong, swap back — it takes seconds.
- task: AzureWebApp@1
displayName: Deploy to staging slot
inputs:
azureSubscription: my-service-connection
appName: my-web-app
deployToSlotOrASE: true
resourceGroupName: my-rg
slotName: staging
- task: AzureAppServiceManage@0
displayName: Swap staging to production
inputs:
azureSubscription: my-service-connection
action: Swap Slots
webAppName: my-web-app
resourceGroupName: my-rg
sourceSlot: stagingRollback with Kubernetes (AKS)
On AKS, every deployment is versioned by Kubernetes. If a bad deployment goes out, you can roll back to the previous revision in a single command:
# Roll back to the previous deployment revision
kubectl rollout undo deployment/myapp -n production
# Or roll back to a specific revision
kubectl rollout undo deployment/myapp --to-revision=3 -n production
# Check rollout status
kubectl rollout status deployment/myapp -n productionFor automated rollback, configure a Kubernetes liveness probe and readiness probe on your deployment. If the new pods fail their health checks, Kubernetes will stop the rollout and the old pods remain in service — the pipeline fails visibly rather than silently leaving a broken deployment running.
Want us to build your CI/CD pipeline?
We design and implement production-grade pipelines for Azure workloads — from first commit to automated multi-environment delivery.
Closing Thoughts
A well-designed CI/CD pipeline is not a nice-to-have — it is the difference between a team that ships with confidence and one that dreads release day. GitHub Actions handles CI beautifully: fast, cheap, and tightly integrated with your repository. Azure DevOps handles CD with the governance controls that production environments demand.
Start with the GitHub Actions CI workflow in this guide, get your image building and pushing to ACR, then add the Azure DevOps release pipeline with a staging environment and a single approval gate. That combination alone will transform how your team ships software.



