Azure Pipelines and AKS are powerful tools for building and deploying containerized applications, but their interaction can be a bit of a tightrope walk.

Let’s walk through a typical deployment scenario. Imagine we’re building a simple web app, containerizing it, and pushing it to Azure Container Registry (ACR) using an Azure Pipeline, then deploying that container to an Azure Kubernetes Service (AKS) cluster.

Here’s a snippet of a Dockerfile for a basic Node.js app:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

And here’s a simplified Azure Pipeline YAML (azure-pipelines.yml) that builds this image and pushes it to ACR:

trigger:
- main

variables:
  acrServiceConnection: 'MyAzureContainerRegistryConnection'
  imageName: 'mywebapp'
  tag: '$(Build.BuildId)'

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: AzureCLI@2
  displayName: 'Login to ACR'
  inputs:
    azureSubscription: 'MyAzureSubscriptionConnection'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      az acr login --name myregistry.azurecr.io --expose-token
      docker login myregistry.azurecr.io -u $(acrLoginServer) -p $(acrPassword) # This part is often handled by the 'docker-push' task implicitly with the service connection, but explicit login is good for clarity.

- task: Docker@2
  displayName: 'Build and push Docker image'
  inputs:
    command: 'buildAndPush'
    repository: '$(imageName)'
    dockerfile: '**/Dockerfile'
    containerRegistry: '$(acrServiceConnection)'
    tags: |
      $(tag)
      latest

This pipeline uses the Docker@2 task. It needs an Azure service connection to ACR (acrServiceConnection) and an Azure subscription service connection (MyAzureSubscriptionConnection) for authentication. The acrLoginServer and acrPassword variables are typically inferred by the docker buildAndPush command when a valid ACR service connection is provided.

Now, let’s deploy this to AKS. We’ll use another Azure Pipeline stage or a separate pipeline. This time, we’ll use a Kubernetes manifest file (deployment.yaml) that references our ACR image.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mywebapp-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mywebapp
  template:
    metadata:
      labels:
        app: mywebapp
    spec:
      containers:
      - name: mywebapp
        image: myregistry.azurecr.io/mywebapp:$(tag) # $(tag) would be passed in from the pipeline
        ports:
        - containerPort: 3000

And the pipeline task to apply this:

# In azure-pipelines.yml, within a deployment stage
- task: KubernetesManifest@0
  displayName: 'Deploy to AKS'
  inputs:
    action: 'deploy'
    kubernetesServiceConnection: 'MyAKSConnection' # Service connection to your AKS cluster
    manifests: 'deployment.yaml'
    containers: 'myregistry.azurecr.io/mywebapp:$(tag)'

The KubernetesManifest@0 task uses a Kubernetes service connection (MyAKSConnection) that’s configured in Azure DevOps to point to your AKS cluster. It takes the deployment.yaml and applies it. The containers input tells the task which image to substitute if you’re using variable substitution within your manifest.

The core problem these tools solve is the automation of complex, error-prone manual processes. Instead of a developer logging into a server, manually building a Docker image, pushing it, and then SSHing into a Kubernetes cluster to kubectl apply, these pipelines orchestrate the entire lifecycle. They provide version control for your infrastructure (IaC), consistent build and deployment environments, and traceability.

Internally, Azure Pipelines uses agents (either Microsoft-hosted or self-hosted) to execute the tasks. For Docker tasks, these agents have Docker installed. For Kubernetes tasks, they have kubectl installed and configured using the provided service connection credentials. The service connections themselves are secure tokens or service principals that grant Azure DevOps the necessary permissions to interact with Azure resources like ACR and AKS.

The most surprising thing about managing these deployments is how often the imagePullSecrets configuration for AKS clusters is overlooked when pulling from private registries like ACR. Even if your AKS cluster has permissions to push to ACR (via its managed identity), it still needs explicit credentials to pull images from that same private registry. This is often handled by creating a Kubernetes secret of type docker-registry and referencing it in the imagePullSecrets field of your Pod or Deployment spec. Azure DevOps can automate this creation for you.

The next concept you’ll likely grapple with is managing different environments (dev, staging, prod) with distinct configurations and how to roll out changes safely using deployment strategies like blue-green or canary deployments within AKS.

Want structured learning?

Take the full DevOps & Platform Engineering course →