Deploying containerized workloads to Azure Kubernetes Service (AKS) requires a seamless, secure bridge between source control, container registries, and the orchestration cluster. Transitioning from manual deployments to a fully automated Continuous Integration and Continuous Deployment (CI/CD) pipeline eliminates human error, ensures immutable image tagging, and accelerates release cycles.

This guide details the complete end-to-end process of provisioning an Azure Container Registry (ACR), configuring secure cluster access, generating GitHub Actions credentials, and deploying application manifests automatically.

Phase 1: Provisioning and Integrating the Container Registry

The Azure Container Registry (ACR) serves as the private repository for your Docker images. Before Kubernetes can run your application, it needs a secure place to pull these images from.

Overcoming the Provider Registration Error

When provisioning an ACR for the first time in an Azure subscription, you will encounter a MissingSubscriptionRegistration error if the specific Azure Resource Provider is not activated.

Activate the provider using the Azure CLI:

Bash

Code
az provider register --namespace Microsoft.ContainerRegistry

Check the registration state to ensure it reads "Registered" before moving forward:

Bash

Code
az provider show -n Microsoft.ContainerRegistry --query "registrationState"

Creating the Registry and Attaching to AKS

With the provider active, create the registry. ACR names must be globally unique across Azure and contain only lowercase alphanumeric characters. The Basic SKU is optimal for standard CI/CD workflows and learning environments.

Bash

Code
az acr create --resource-group linux-admin-course --name mudassaracr99 --sku Basic

Next, authorize the AKS cluster to pull images from this new registry. This creates a secure acrpull role assignment for the cluster's managed identity, eliminating the need to manually manage Kubernetes Image Pull Secrets.

Bash

Code
az aks update -n mudassar-kubi-course -g linux-admin-course --attach-acr mudassaracr99

Phase 2: Establishing Secure Pipeline Identities

For GitHub Actions to interact with your Azure environment, it requires an identity. You must create an Azure Active Directory Service Principal with Contributor rights scoped strictly to your resource group.

The Shell Syntax Trap

A common issue when generating service principals is blindly copying documentation that uses angle brackets (e.g., <your-subscription-id>). In Linux bash environments, the < and > symbols are interpreted as file redirection operators, which causes the command to fail with a "No such file or directory" error.

First, retrieve your exact Azure Subscription ID:

Bash

Code
az account show --query id --output tsv

Construct the command using the raw ID without brackets:

Bash

Code
az ad sp create-for-rbac --name "GitHub-Actions-AKS" --role contributor --scopes /subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/linux-admin-course --json-auth

Copy the entire JSON payload outputted by this command. In your GitHub repository, navigate to Settings > Secrets and variables > Actions, create a new secret named AZURE_CREDENTIALS, and paste the JSON block as its value.

Phase 3: Defining the CI/CD Pipeline Workflow

The GitHub Actions workflow serves as the engine for the deployment. It dictates how the code is built, tagged, pushed to the registry, and eventually deployed to the cluster.

Create a .github/workflows/deploy.yml file in your repository:

YAML

Bash
name: Build and Deploy to AKS

on:
  push:
    branches:
      - main

env:
  ACR_NAME: mudassaracr99
  CLUSTER_NAME: mudassar-kubi-course
  RESOURCE_GROUP: linux-admin-course
  IMAGE_NAME: my-aks-app 

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Azure Login
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Login to ACR
        run: az acr login --name ${{ env.ACR_NAME }}

      # Builds the image and tags it with the unique git commit hash
      - name: Build and Push Docker Image
        run: |
          docker build -t ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.sha }} .
          docker push ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Set AKS Context
        uses: azure/aks-set-context@v3
        with:
          resource-group: ${{ env.RESOURCE_GROUP }}
          cluster-name: ${{ env.CLUSTER_NAME }}

      # Applies the Kubernetes manifests, dynamically injecting the image tag
      - name: Deploy to AKS
        uses: Azure/k8s-deploy@v5
        with:
          manifests: |
            manifests/deployment.yaml
            manifests/service.yaml
          images: |
            ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}

Phase 4: Structuring the Kubernetes Manifests

For the pipeline to deploy the application successfully, Kubernetes requires imperative instructions on how to run and expose the workloads. These files should be stored in a manifests/ directory at the root of your repository.

The Deployment Manifest

Create manifests/deployment.yaml. This file dictates the pod replicas, container specifications, and resource constraints.

YAML

Code
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-aks-app-deployment
  labels:
    app: my-aks-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-aks-app
  template:
    metadata:
      labels:
        app: my-aks-app
    spec:
      containers:
      - name: my-aks-app-container
        image: mudassaracr99.azurecr.io/my-aks-app:latest
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 250m
            memory: 256Mi

Note: The image tag is deliberately set to :latest here. During execution, the Azure/k8s-deploy action in the GitHub workflow automatically intercepts this file and replaces :latest with the exact ${{ github.sha }} commit hash. This guarantees that Kubernetes detects a configuration change and gracefully rolls over to the new image.

The Service Manifest

Create manifests/service.yaml. This defines the networking rules, exposing the application to the internet via an Azure Load Balancer.

YAML

Code
apiVersion: v1
kind: Service
metadata:
  name: my-aks-app-service
  labels:
    app: my-aks-app
spec:
  type: LoadBalancer
  selector:
    app: my-aks-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

By pushing these manifests, the Dockerfile, and the GitHub Actions YAML to the main branch, the infrastructure automatically takes over. The source code is built into an immutable artifact, stored securely in Azure, and orchestrated across the Kubernetes cluster without any further manual intervention.