CI/CD for Azure DevOps: YAML, AKS, and Automation
Manual deployment through the Azure DevOps web interface becomes unreliable when releasing once a week. Errors, downtime, lost secrets—a familiar picture. On average, 3 incidents per month due to manual deployment. We design multi-stage YAML pipelines integrating ACR, AKS, and Key Vault to automate deployment without surprises. With over 30 projects in migrating and configuring Azure DevOps, our experience helps anticipate typical mistakes and avoid downtime.
Problems We Solve
Classic pipelines instead of YAML. Microsoft has deprecated Classic Release Pipelines, but many companies still use them. This is technical debt: Classic doesn't support GitOps, approval checks at the environment level, or multi-stage scenarios. Classic pipelines lead to twice as many errors during deployment. We help migrate to YAML without stopping current releases.
No secure connection to Azure resources. Service Principals with secrets are risky: secrets can expire, end up in logs or repositories. Workload Identity Federation (WIF) solves this. The pipeline gets a temporary token via OIDC—no rotations, no leaks. Workload Identity Federation
Long and unstable deployments. Without a multi-stage pipeline, staging and production deployments are often done manually or via separate release pipelines. This slows down delivery and increases error probability. We build a single YAML pipeline with conditional stages that automatically trigger on push to main or a tag. Comparison: YAML pipelines reduce the delivery cycle time by 3x compared to Classic due to step automation and parallel execution.
How We Do It
Let's review a real case: an 8-person team, .NET microservices, AKS, PostgreSQL via Azure Database. Previously, deployment looked like this: build in Classic Build, manual push to ACR, then a release pipeline with many manual steps. We proposed a single YAML pipeline with three stages:
- Build: npm ci, lint, test, docker build & push to ACR.
- DeployStaging: automatically on push to main; Helm upgrade with pod ready check.
- DeployProduction: only on tag v*; requires approval from tech lead; uses canary strategy (replicaCount=3, traffic split).
Here's a simplified version of such a pipeline:
# azure-pipelines.yml
trigger:
branches:
include:
- main
tags:
include:
- 'v*'
variables:
containerRegistry: 'myapp-acr'
imageRepository: 'myapp'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.SourceVersion)'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildJob
pool:
vmImage: 'ubuntu-22.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: |
npm ci
npm run lint
npm run test -- --reporters=jest-junit
displayName: 'Build and Test'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
- task: Docker@2
displayName: 'Build and Push to ACR'
inputs:
command: buildAndPush
containerRegistry: '$(containerRegistry)'
repository: '$(imageRepository)'
dockerfile: '$(Dockerfile)'
tags: |
$(tag)
latest
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployStaging
environment: 'staging'
pool:
vmImage: 'ubuntu-22.04'
strategy:
runOnce:
deploy:
steps:
- task: HelmDeploy@0
inputs:
connectionType: 'Azure Resource Manager'
azureSubscription: 'MyAzureSubscription'
azureResourceGroup: 'myapp-rg'
kubernetesCluster: 'myapp-aks-staging'
namespace: 'staging'
command: 'upgrade'
chartType: 'FilePath'
chartPath: '$(Pipeline.Workspace)/charts/myapp'
releaseName: 'myapp'
overrideValues: 'image.tag=$(tag)'
waitForExecution: true
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: DeployStaging
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
jobs:
- deployment: DeployProduction
environment: 'production'
pool:
vmImage: 'ubuntu-22.04'
strategy:
runOnce:
deploy:
steps:
- task: HelmDeploy@0
inputs:
connectionType: 'Azure Resource Manager'
azureSubscription: 'MyAzureSubscription'
azureResourceGroup: 'myapp-rg'
kubernetesCluster: 'myapp-aks-prod'
namespace: 'production'
command: 'upgrade'
chartType: 'FilePath'
chartPath: '$(Pipeline.Workspace)/charts/myapp'
releaseName: 'myapp'
overrideValues: 'image.tag=$(tag),replicaCount=3'
waitForExecution: true
environment: 'production' in this pipeline is not just a name. In Azure DevOps Environments, we added Approval Checks: before deploying to production, approval from the department head is required. We also configured business hours (deployment only 9 AM to 6 PM UTC) and automatic rollback on failure.
Why Abandon Classic Pipelines?
Classic Pipelines are legacy that hinder automation. They lack versioning, environment-based approval checks, and conditional stage support. The comparison shows the difference:
| Parameter | Classic Pipelines | YAML Pipelines |
|---|---|---|
| Versioning | No (only in file) | In repository, via PR |
| Approval checks | Only at release level | Environment-level: flexible, with functions |
| GitOps support | No | Yes (integration with GitHub Actions or ArgoCD) |
| Multi-stage | Only via Linked Pipelines | Native, one file |
| Reusability | No (templates only via copy) | Via templates and extends |
YAML pipelines are the minimum standard for modern CI/CD.
How to Connect Azure Key Vault to a Pipeline?
Without Key Vault, secrets often end up as open pipeline variables. We configure the AzureKeyVault@2 task:
---
- task: AzureKeyVault@2
inputs:
azureSubscription: 'MyAzureSubscription'
KeyVaultName: 'myapp-keyvault'
SecretsFilter: 'DB-PASSWORD,API-KEY'
RunAsPreJob: true
---After this, $(DB-PASSWORD) is available as a variable, and its value is masked in logs. If the password changes in Key Vault, the pipeline automatically picks up the change without restart.
What's Included
We provide a complete set of services for setting up CI/CD pipelines:
- Infrastructure audit: analysis of existing pipelines, Service Connections, Environments, and RBAC.
- Design: multi-stage pipeline architecture, deployment strategy selection (canary, blue-green, rolling).
- Implementation: writing YAML templates, configuring WIF, connecting Key Vault and ACR.
- Testing: running the pipeline in a test environment, verifying approval checks and rollbacks.
- Documentation: pipeline description, instructions for adding new services.
- Training: demo session for the team, Q&A.
- Support: one month of post-release support to resolve any issues.
Work Process
- Analysis – audit current pipelines, agree on requirements (1-2 days).
- Design – develop pipeline scheme, select strategies, agree (1 day).
- Implementation – write YAML code, configure Environments, Service Connections (2-4 days).
- Test – run pipelines in staging, measure time, adjust (1-2 days).
- Deploy – move infrastructure under pipeline management (1 day).
- Training and support – hand over documentation, Q&A, 1 month of support.
Estimated Timelines
| Scope | Timeline |
|---|---|
| New YAML pipeline with AKS deployment | 2–3 days |
| Migration from Classic + refactoring | 3–5 days |
| Multiple environments + WIF + Key Vault | 4–6 days |
Cost is calculated individually after an infrastructure audit.
Get a consultation on CI/CD pipeline setup—our engineers will evaluate your infrastructure and propose an optimal solution. Request a CI/CD infrastructure audit to identify bottlenecks and increase release speed by 40%. Contact us to discuss your project.
Source: Microsoft Azure Pipelines documentation







