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
az provider register --namespace Microsoft.ContainerRegistry
Check the registration state to ensure it reads "Registered" before moving forward:
Bash
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
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
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
az account show --query id --output tsv
Construct the command using the raw ID without brackets:
Bash
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
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
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
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.
Join the conversation! Your thoughts help the community grow.