CI/CD Automation with Azure DevOps: YAML, AKS, Key Vault

Manual deployment in Azure DevOps turns into a source of errors and downtime, especially when releases are frequent. We automate the process with multi-stage YAML pipelines, integrating ACR, AKS, and Key Vault for secure and reliable delivery. Our team delivers the project turnkey—from pipeline design to implementation and ongoing support, ensuring stable operation without missed deadlines.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1359
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

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

  1. Analysis – audit current pipelines, agree on requirements (1-2 days).
  2. Design – develop pipeline scheme, select strategies, agree (1 day).
  3. Implementation – write YAML code, configure Environments, Service Connections (2-4 days).
  4. Test – run pipelines in staging, measure time, adjust (1-2 days).
  5. Deploy – move infrastructure under pipeline management (1 day).
  6. 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