Deploy and Operate Azure Container Apps
In this exercise you will deploy and operate the Contoso Analytics platform, an event-driven microservices application built on Azure Container Apps. You’ll work with infrastructure deployment, container image management, HTTP auto-scaling, traffic splitting for blue-green deployments, and Container Apps Jobs for batch processing. Once everything is up-and-running, you will integrate a DevOps strategy using Azure DevOps CI/CD pipelines.
This exercise should take approximately 150 minutes to complete.
Table of Contents
- Before you start
- Exercise 1: Baseline Scenario Deployment
- Exercise 2: Managing & Operating Azure Container Apps
- Exercise 3: Azure DevOps CI/CD Pipelines
Before you start
Before you can start the exercises, you will need to:
- An Azure subscription with Contributor access
- Azure CLI version 2.50 or later installed
- Azure Developer CLI - AZD installed (for Option B deployment choice)
- Docker Desktop installed and running
-
An Azure DevOps Organization and Project with Contributor access
-
Verify your tools are ready:
# Check Azure CLI version (requires 2.50+) az version# Check Docker is running; if it fails, start Docker Desktop and wait for the GUI to inform you it is ready to run containers docker info# Login to Azure az login# Set your subscription (replace with your subscription ID or name) az account set --subscription "<YOUR_SUBSCRIPTION_ID>"# Verify current subscription az account show --query "{Name:name, SubscriptionId:id}" -o table -
Clone the repository:
# Clone the repository git clone https://github.com/MicrosoftLearning/az-2003-deploy-cloud-native-applications-using-azure-container-apps.git# Navigate to the project directory cd .\az-2003-deploy-cloud-native-applications-using-azure-container-apps -
Set your environment variables that will be used throughout the exercise:
# Set your environment name (use lowercase, no special characters) $ENV_NAME = "contapp" # Example: "john-lab"Note: Replace
contappwith a unique name. This will be used to create globally unique resource names.# Set the Azure region $LOCATION = "eastus2" # Choose your Azure region# Get your user principal ID (for Key Vault access) $PRINCIPAL_ID = (az ad signed-in-user show --query id -o tsv)# Verify the principal ID was retrieved Write-Host "Principal ID: $PRINCIPAL_ID"
Exercise 1: Baseline Scenario Deployment
In this exercise, you will deploy the foundational infrastructure for the Contoso Analytics platform. You’ll provision Azure Container Apps Environment, Container Registry, Key Vault, and supporting resources using either Azure CLI with Bicep templates or the Azure Developer CLI (azd). By the end of this exercise, you’ll have a fully operational container platform ready for application deployment.
Task 1: Deploy Infrastructure
In this task, you’ll deploy the Azure Container Apps infrastructure. You can choose between two deployment methods:
| Method | Description | Best For |
|---|---|---|
| Option A: Azure CLI + Bicep | Deploy infrastructure step-by-step using Azure CLI and Bicep templates | Learning Bicep, granular control, understanding each component |
| Option B: Azure Developer CLI (azd) | One-command deployment of infrastructure AND application code | Quick setup, CI/CD workflows, production scenarios |
Important: Choose ONE deployment option below. If you are rather new to Docker and Container Registry tasks such as building, deploying and managing containers, we recommend using Option A (az cli), as it walks through all steps manually. If you are already familiar with Docker, Azure Container Registry and Azure Bicep deployments, choose Option B (azd), since azd builds and deploys the container images automatically.
Option A: Deploy with Azure CLI and Bicep
Use this option if you want to understand the Bicep templates and deploy infrastructure step-by-step.
Review the Bicep Template
-
Before deploying, examine the main Bicep template to understand what resources will be created:
# Open the main Bicep file to review Get-Content .\infra\main.bicep | Select-Object -First 80 -
Note the resources that will be created by the template:
- Resource Group
- Log Analytics Workspace
- User-Assigned Managed Identity
- Azure Container Registry
- Container Apps Environment
- Azure Service Bus Namespace
- Azure Key Vault
- Container Apps (with placeholder images)
- Container Apps Jobs (with placeholder images)
Deploy the Infrastructure
-
Deploy the Bicep template using Azure CLI:
# Deploy infrastructure at subscription scope az deployment sub create ` --name "contapp-$ENV_NAME" ` --location $LOCATION ` --template-file .\infra\main.bicep ` --parameters environmentName=$ENV_NAME ` --parameters location=$LOCATION ` --parameters principalId=$PRINCIPAL_ID ` --query "properties.outputs" -o jsonNote: This deployment takes approximately 5-8 minutes to complete.
Capture Deployment Outputs
-
After deployment completes, capture the output values for later use:
# Get deployment outputs $OUTPUTS = az deployment sub show ` --name "contapp-$ENV_NAME" ` --query "properties.outputs" -o json | ConvertFrom-Json# Extract key values $RG = $OUTPUTS.AZURE_RESOURCE_GROUP.value $ACR = $OUTPUTS.CONTAINER_REGISTRY_LOGIN_SERVER.value $ACR_NAME = $OUTPUTS.CONTAINER_REGISTRY_NAME.value $CAE_NAME = $OUTPUTS.CONTAINER_APPS_ENVIRONMENT_NAME.value $DASHBOARD_URL = $OUTPUTS.DASHBOARD_URL.value $INGESTION_URL = $OUTPUTS.INGESTION_SERVICE_URL.value $HELLO_API_URL = $OUTPUTS.HELLO_API_URL.value # Display captured values Write-Host "=========================================" Write-Host "Resource Group: $RG" Write-Host "Container Registry: $ACR" Write-Host "Environment: $CAE_NAME" Write-Host "Dashboard URL: $DASHBOARD_URL" Write-Host "Ingestion Service URL: $INGESTION_URL" Write-Host "Hello API URL: $HELLO_API_URL" Write-Host "========================================="
Verify Initial Deployment
-
Check that all resources were created:
# List all resources in the resource group az resource list -g $RG --query "[].{Name:name, Type:type}" -o table# List Container Apps az containerapp list -g $RG --query "[].{Name:name, URL:properties.configuration.ingress.fqdn}" -o table# List Container Apps Jobs az containerapp job list -g $RG --query "[].{Name:name, TriggerType:properties.configuration.triggerType}" -o tableNote: You should see 3 Container Apps, ingestion-service, hello-api and dashboard. You should see 3 Container App Jobs, data-processor-parallel, -scheduled and -manual
Note: At this point, the Container Apps are running with placeholder images (
containerapps-helloworld). Continue with the Build Container Images section below.
Build Container Images
In this section, you’ll build custom container images and push them to Azure Container Registry.
-
Authenticate to your container registry:
# Login to ACR az acr login --name $ACR_NAME -
Build and push the ingestion service image:
# Navigate to the ingestion-service directory cd src/ingestion-service# Build the container image docker build -t "$ACR/ingestion-service:v1" .# Push to Azure Container Registry docker push "$ACR/ingestion-service:v1"# Return to project root cd ../.. -
Build and push the dashboard image:
# Navigate to the dashboard directory cd src/dashboard# Build the container image docker build -t "$ACR/dashboard:v1" .# Push to Azure Container Registry docker push "$ACR/dashboard:v1"# Return to project root cd ../.. -
Build and push the Hello API image:
# Navigate to the hello-api directory cd src/hello-api# Build v1 of the Hello API (blue version) docker build -t "$ACR/hello-api:v1" --build-arg APP_VERSION=v1 .# Push to Azure Container Registry docker push "$ACR/hello-api:v1"# Return to project root cd ../.. -
Build and push the demo job image:
# Navigate to the demo-job directory cd src/demo-job# Build the job image docker build -t "$ACR/demo-job:v1" .# Push to Azure Container Registry docker push "$ACR/demo-job:v1"# Return to project root cd ../.. -
Verify all images are available in the registry:
# List all repositories in ACR az acr repository list --name $ACR_NAME -o table# List tags for each repository az acr repository show-tags --name $ACR_NAME --repository ingestion-service -o table az acr repository show-tags --name $ACR_NAME --repository dashboard -o table az acr repository show-tags --name $ACR_NAME --repository hello-api -o table az acr repository show-tags --name $ACR_NAME --repository demo-job -o table
Deploy Container Images to Container Apps
Now update the deployed Container Apps to use your custom images.
-
Update the ingestion-service with the custom image:
# Update the ingestion-service with the custom image az containerapp update ` --name ingestion-service ` --resource-group $RG ` --image "$ACR/ingestion-service:v1" # Verify the update az containerapp show -n ingestion-service -g $RG ` --query "{Name:name, Image:properties.template.containers[0].image, Status:properties.runningStatus}" -o table -
Update the dashboard with the custom image:
# Update the dashboard with the custom image az containerapp update ` --name dashboard ` --resource-group $RG ` --image "$ACR/dashboard:v1"# Verify the update az containerapp show -n dashboard -g $RG ` --query "{Name:name, Image:properties.template.containers[0].image, Status:properties.runningStatus}" -o table -
Update the hello-api with the custom image:
# Update the hello-api with the custom image az containerapp update ` --name hello-api ` --resource-group $RG ` --image "$ACR/hello-api:v1"# Verify the update az containerapp show -n hello-api -g $RG ` --query "{Name:name, Image:properties.template.containers[0].image, Status:properties.runningStatus}" -o table -
Update all three jobs with the demo-job image:
# Update all three jobs with the demo-job image az containerapp job update -n data-processor-scheduled -g $RG --image "$ACR/demo-job:v1" az containerapp job update -n data-processor-manual -g $RG --image "$ACR/demo-job:v1" az containerapp job update -n data-processor-parallel -g $RG --image "$ACR/demo-job:v1"# Verify the updates az containerapp job list -g $RG ` --query "[].{Name:name, Image:properties.template.containers[0].image}" -o table -
Verify all services are operational:
# Check all Container Apps are running az containerapp list -g $RG ` --query "[].{Name:name, Status:properties.runningStatus, Replicas:properties.template.scale.minReplicas}" -o table# Test the endpoints Write-Host "`nTesting Dashboard..." Invoke-RestMethod -Uri "$DASHBOARD_URL/health" -TimeoutSec 30 Write-Host "`nTesting Ingestion Service..." Invoke-RestMethod -Uri "$INGESTION_URL/health" -TimeoutSec 30 Write-Host "`nTesting Hello API..." Invoke-RestMethod -Uri "$HELLO_API_URL/api/version" -TimeoutSec 30Note: You have completed the manual deployment using Azure CLI and Bicep. Proceed to Exercise 2, Task 1: Implement HTTP Auto-Scaling.
Option B: Deploy with Azure Developer CLI (azd)
Use this option for a streamlined one-command deployment that handles everything automatically:
- Provisions all Azure infrastructure using Bicep templates
- Builds all container images locally using Docker
- Pushes images to Azure Container Registry
- Deploys all services to Container Apps
- Configures Container Apps Jobs
Time saver: If you choose this option, you can skip directly to Exercise 2, Task 1: Implement HTTP Auto-Scaling after deployment completes.
Prerequisites for azd
-
Ensure Azure Developer CLI is installed:
# Check if azd is installed azd version# If not installed, install it (Windows winget) winget install microsoft.azdNote: If you are not on Windows or prefer PowerShell instead of Winget, use this command:
powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression"
Initialize and Deploy with azd
-
Login to Azure with azd:
# Login to Azure azd auth login -
Initialize a new azd environment:
# Start from the cloned repo folder cd .\az-2003-deploy-cloud-native-applications-using-azure-container-apps# Create a new environment (use the same name as ENV_NAME) azd env new $ENV_NAME# Set the Azure location azd env set AZURE_LOCATION $LOCATION -
Deploy everything with a single command:
# Deploy infrastructure AND application code azd upNote: This command will:
- Provision all Azure infrastructure using the Bicep templates
- Build all container images locally using Docker
- Push images to Azure Container Registry
- Deploy all services to Container Apps
- Configure Container Apps Jobs
The entire process takes approximately 10-15 minutes.
-
When prompted:
- Select your Azure subscription
- Confirm the location (or press Enter to accept the default)
Capture azd Deployment Outputs
-
After deployment completes, capture the environment variables:
# Get all environment values from azd and set them as PowerShell variables azd env get-values | ForEach-Object { if ($_ -match '^([^=]+)=(.*)$') { Set-Variable -Name $matches[1] -Value ($matches[2] -replace '^"|"$', '') } }# The variables are now available directly (matching Bicep output names) $RG = $AZURE_RESOURCE_GROUP $ACR = $CONTAINER_REGISTRY_LOGIN_SERVER $ACR_NAME = $CONTAINER_REGISTRY_NAME $CAE_NAME = $CONTAINER_APPS_ENVIRONMENT_NAME# Set aliases to match Option A variable names (for consistency in later tasks) $INGESTION_URL = $INGESTION_SERVICE_URL# Display captured values Write-Host "=========================================" Write-Host "Resource Group: $RG" Write-Host "Container Registry: $ACR" Write-Host "Environment: $CAE_NAME" Write-Host "Dashboard URL: $DASHBOARD_URL" Write-Host "Ingestion Service URL: $INGESTION_URL" Write-Host "Hello API URL: $HELLO_API_URL" Write-Host "========================================="
Verify azd Deployment
-
Check the deployed resources:
# List all Container Apps (should show your custom images, not placeholders) az containerapp list -g $RG --query "[].{Name:name, Image:properties.template.containers[0].image}" -o table# List Container Apps Jobs az containerapp job list -g $RG --query "[].{Name:name, TriggerType:properties.configuration.triggerType}" -o table# Test the endpoints Write-Host "`nTesting Dashboard..." Invoke-RestMethod -Uri "$DASHBOARD_URL/health" -TimeoutSec 30 Write-Host "`nTesting Ingestion Service..." Invoke-RestMethod -Uri "$INGESTION_URL/health" -TimeoutSec 30Note: Since azd deployed the actual application images, proceed directly to Exercise 2, Task 1: Implement HTTP Auto-Scaling.
Exercise 2: Managing & Operating Azure Container Apps
In this exercise, you will explore the operational capabilities of Azure Container Apps. You’ll implement HTTP-based auto-scaling to handle variable traffic loads, configure traffic splitting for blue-green deployments and A/B testing, and work with Container Apps Jobs for batch processing workloads. These tasks demonstrate real-world scenarios for managing production containerized applications.
Task 1: Implement HTTP Auto-Scaling
In this task, you will observe how Azure Container Apps automatically scales the ingestion-service based on HTTP traffic.
Check Initial Replica Count
-
Verify the current number of replicas:
# Check the current number of replicas az containerapp replica list -n ingestion-service -g $RG -o tableNote: The output of this command should show 1 replica (minReplicas is set to 1)
Review Scaling Configuration
-
View the scaling rules configured for the ingestion-service:
# View the scaling rules configured for the ingestion-service az containerapp show -n ingestion-service -g $RG ` --query "properties.template.scale" -o jsonNote: The
concurrentRequestsis set to2, meaning the service will scale up when there are more than 2 concurrent requests per replica.
Open the Dashboard Container App
-
Open the dashboard Container App in your browser:
# Display the dashboard URL Write-Host "Open this URL in your browser: $DASHBOARD_URL"# Or open directly (Windows) Start-Process $DASHBOARD_URL
Trigger Load and Observe Scaling
- In the dashboard, click the 🔥 Send 100 Events (Heavy Load) button
- Watch the status message showing events being sent
-
While the load test is running, monitor the replica count:
# Run this command multiple times during the load test to see scaling az containerapp replica list -n ingestion-service -g $RG -o table# Or watch continuously (run in a separate terminal) while ($true) { $count = (az containerapp replica list -n ingestion-service -g $RG -o json | ConvertFrom-Json).Count Write-Host "$(Get-Date -Format 'HH:mm:ss') - Replica count: $count" Start-Sleep -Seconds 5 }
Observe Scale-Down
-
After the load test completes, wait for the cooldown period (default 5 minutes) and observe the replicas scaling back down:
# Check replica count after load subsides az containerapp replica list -n ingestion-service -g $RG -o table
View Scaling Metrics in Azure Portal
- Navigate to the Azure Portal at
https://portal.azure.com - Go to your Resource Group and select ingestion-service
- Click on Metrics
- Add the metric Replica Count
-
Observe the scaling behavior over time
Note: You should have observed the ingestion-service scaling from 1 replica up to multiple replicas during load, then back down after the cooldown period.
Task 2: Implement Traffic Splitting
In this task, you will deploy a second version of the Hello API and implement traffic splitting for a blue-green deployment.
Verify Version 1 is Running
-
Confirm the current version is deployed:
# Open Hello API in browser Start-Process $HELLO_API_URL# Or test via CLI Invoke-RestMethod -Uri "$HELLO_API_URL/api/version" -
You should see a blue “v1” badge on the page.
Enable Multiple Revision Mode
-
Enable multiple revision mode to split traffic between versions:
# Enable multiple revision mode az containerapp revision set-mode ` --name hello-api ` --resource-group $RG ` --mode Multiple# Verify the mode change az containerapp show -n hello-api -g $RG ` --query "properties.configuration.activeRevisionsMode" -o tsv
Build and Push Version 2
-
Build and push version 2 of the Hello API:
# Navigate to the hello-api directory cd src/hello-api# Build v2 (green version) docker build -t "$ACR/hello-api:v2" --build-arg APP_VERSION=v2 .# Push to ACR docker push "$ACR/hello-api:v2"# Return to project root cd ../..
Deploy Version 2 as a New Revision
-
Deploy v2 as a new revision with a custom suffix:
# Deploy v2 as a new revision with a custom suffix az containerapp update ` --name hello-api ` --resource-group $RG ` --image "$ACR/hello-api:v2" ` --revision-suffix v2 ` --set-env-vars "APP_VERSION=v2"
List All Revisions
-
View all available revisions:
# List all revisions of hello-api az containerapp revision list -n hello-api -g $RG ` --query "[].{Name:name, Active:properties.active, TrafficWeight:properties.trafficWeight, Created:properties.createdTime}" -o tableNote: At this point, 100% of traffic goes to the latest revision (v2).
Split and Test Traffic 50/50
From the Azure Portal, configure traffic splitting between versions:
- Navigate to the Resource Group and select the hello-Api Container App
- Select Application / Revisions and replicas
- Notice 2 revisions, hello-api–v2 and hello-api–00000
- The hello-api–v2 currently has 100% traffic load. Update this to 50%
- Update the same 50% weight for the other revision
- Save your changes
- Navigate back to the Overview section of the hello-api Container Apps and select the Application URL
- Refresh the browser multiple times; you should see the blue and green version alternating
Complete the Migration
-
Shift 100% of traffic to v2:
# Route all traffic to v2 az containerapp ingress traffic set ` --name hello-api ` --resource-group $RG ` --revision-weight "hello-api--v2=100"# Verify az containerapp ingress traffic show -n hello-api -g $RG -o table
(Optional) Rollback to v1
-
If needed, you can instantly roll back:
# Rollback to v1 az containerapp ingress traffic set ` --name hello-api ` --resource-group $RG ` --revision-weight "$REV_V1=100"Note: You have successfully implemented blue-green deployment with traffic splitting, allowing you to gradually migrate users between versions.
Task 3: Work with Container Apps Jobs
In this task, you will work with Container Apps Jobs for batch processing tasks.
List All Jobs
-
View all Container Apps Jobs:
# List all Container Apps Jobs az containerapp job list -g $RG ` --query "[].{Name:name, TriggerType:properties.configuration.triggerType, Schedule:properties.configuration.scheduleTriggerConfig.cronExpression}" -o table -
You should see three jobs:
Name Trigger Type Schedule data-processor-scheduled Schedule */2 * * * * data-processor-manual Manual - data-processor-parallel Manual -
View Scheduled Job Executions
-
The scheduled job runs every 2 minutes. Check its execution history:
# List executions for the scheduled job az containerapp job execution list ` --name data-processor-scheduled ` --resource-group $RG ` --query "[].{Name:name, Status:properties.status, StartTime:properties.startTime}" -o tableNote: If no executions appear, wait 2 minutes for the first scheduled run. It is possible that some jobs will show as successful and others as failed.
View Job Logs in the Azure Portal
- From the Resource Group, select the data-processor-scheduled Container Apps
- From the Overview section, select the View link under Execution History
- If you refresh around every 2 minutes, you will see a new job with status Running
- For any of the Jobs, select the Console / System Logs link
- This shows a more detailed log view in Azure Log Analytics with a full detail of the job
Note: You can see the same by navigating to Monitoring / Execution History
Trigger a Manual Job
-
Start the manual job:
# Start the manual job $JOB_EXECUTION = az containerapp job start ` --name data-processor-manual ` --resource-group $RG ` --query "name" -o tsvWrite-Host "Started job execution: $JOB_EXECUTION"# Wait for completion Write-Host "Waiting for job to complete..." Start-Sleep -Seconds 20# Check execution status az containerapp job execution list ` --name data-processor-manual ` --resource-group $RG ` --query "[0].{Name:name, Status:properties.status, StartTime:properties.startTime, EndTime:properties.endTime}" -o table
Trigger a Parallel Job
-
The parallel job runs 3 instances simultaneously:
# Start the parallel job az containerapp job start ` --name data-processor-parallel ` --resource-group $RGWrite-Host "Started parallel job with 3 replicas"# Wait and check status Start-Sleep -Seconds 25# View execution details az containerapp job execution list ` --name data-processor-parallel ` --resource-group $RG ` --query "[0].{Name:name, Status:properties.status}" -o table
View Parallel Execution in Portal
- Navigate to the Azure Portal at
https://portal.azure.com - Go to your Resource Group and select data-processor-parallel
- Click on Execution history
- Click on the latest execution
-
Observe that 3 replicas ran simultaneously
Note: You have successfully worked with Container Apps Jobs, including scheduled jobs, manual triggers, and parallel execution.
Exercise 3: Azure DevOps CI/CD Pipelines
In this exercise, you will implement continuous integration and continuous deployment (CI/CD) pipelines using Azure DevOps. You’ll create an automated pipeline that builds container images, pushes them to Azure Container Registry, and deploys to Azure Container Apps. You’ll also learn how to implement advanced deployment strategies like traffic splitting directly in your pipeline for zero-downtime releases.
Task 1: Deploy with Azure DevOps Pipelines
In this task, you will set up an Azure DevOps pipeline to automate the build and deployment of your Container Apps. This demonstrates how to implement CI/CD for containerized microservices.
Prerequisites for Azure DevOps (if needed)
- Ensure you have access to an Azure DevOps organization. If not, create one:
- Navigate to Azure DevOps at
https://aex.dev.azure.com - Sign in with your Microsoft account
- Click New organization if you don’t have one
- Navigate to Azure DevOps at
-
Create a new project for this exercise:
# Open Azure DevOps in your browser Start-Process "https://dev.azure.com" - In Azure DevOps:
- Click + New project
- Enter project name:
Contoso-ContainerApps - Set visibility to Private
- Click Create
Create an Azure Service Connection
-
Create a service connection to allow Azure DevOps to deploy to your Azure subscription:
- In your Azure DevOps project, go to Project settings (bottom left)
- Under Pipelines, click Service connections
- Click Create service connection
- Select Azure Resource Manager and click Next
- Configure the connection:
- Identity type: App registration (automatic)
- Credential: Workload identity federation
- Scope level: Subscription
- Subscription: Select your Azure subscription
- Resource group: Leave empty (subscription-level access)
- Service connection name:
AzureServiceConnection
- Click Save
Note: The service connection name must match the
azureSubscriptionvariable in the pipeline YAML file.
Initialize a Git Repository
-
Initialize a Git repository and push your code to Azure DevOps:
# Navigate to your project directory cd c:\azd-contapp-demo-v2# Initialize Git repository git init# Add all files git add .# Create initial commit git commit -m "Initial commit: Contoso Analytics Container Apps"
Add Azure DevOps as Remote and Push
- Get the repository URL from Azure DevOps:
- In your Azure DevOps project, click Repos
- Click Files
- Copy the Clone URL (HTTPS)
-
Update the remote and push:
# Add Azure DevOps as remote (replace with your Azure DevOps Project and Repo URL) git remote set-url origin https://dev.azure.com/YOUR_ORG/YOUR_PROJECT/_git/YOUR_REPO# Push to Azure DevOps git push -u origin mainNote: You may be prompted to authenticate. Use your Azure DevOps credentials or a Personal Access Token (PAT).
Update Pipeline Variables
- Before running the pipeline, update the deployment variables in the correct YAML file for your Azure DevOps setup to match your deployment:
Ubuntu ADO Agent:
# Open the pipeline file
code .ado/azure-pipelines.yml
Windows ADO Agent:
# Open the pipeline file
code .ado/azure-pipelines-windows.yml
- Modify the following variables with their correct values:
- azureSubscription: the name of the ADO Service Connection set up earlier (default=AzureServiceConnection)
- resourceGroupName: Name of the Resource Group with your Azure Resources already used for this project
- ACR Login Server: The FQDN of the Azure Container Registry already used in this project
- ACR Name: The short name of the Azure Container Registry already used in this project
- Azure Region: The Azure region location name already used for this project
-
Depending on the setup of your Azure DevOps Agent Pool, you might also need to change the pipeline pool settings syntax:
- When using self-hosted agent
pool: name: $(agentPool)- When using Azure Hosted agent
pool: vmImage: 'ubuntu-latest' #or 'windows-latest if you are on the azure-pipelines-windows.yml' -
Save the file and commit the change:
git add . git commit -m "Update environment name for pipeline" git push
Create the Pipeline
- In Azure DevOps:
- Go to Pipelines > Pipelines
- Click Create Pipeline (or New pipeline)
- Select Azure Repos Git
- Select your repository
- Select Existing Azure Pipelines YAML File
- Choose the correct pipeline for your setup:
- Branch: main
- Path:
./ado/azure-pipelines.yml(for Ubuntu ADO Agent)./ado/azure-pipelines-windows.yml(for Windows ADO Agent)
- Click Run to save the changes and kick-off the pipeline execution
Pipeline Approvals
- Before the pipeline starts, you might get prompted for an approval. This is managed through environments, simulating how a DevOps team can keep control of the actual deployment to production. Confirm the approval request.
Review the Pipeline Structure
-
While the pipeline is running, review some details. The pipeline has three stages:
Stage Description Build & Test Builds Docker images for all services (dashboard, ingestion-service, hello-api, demo-job) and validates Bicep templates Push Images to ACR Pushes built images to your Azure Container Registry Deploy to Container Apps Updates all Container Apps and Jobs with the new images -
Note the key features:
- Parallel builds: All services build simultaneously
- Artifact publishing: Docker images are saved as pipeline artifacts
- Dynamic outputs: Resource names are retrieved from your existing deployment
- Environment gates: Deployment requires approval (optional)
Monitor Pipeline Progress
- Watch the pipeline stages:
- Build & Test: Should complete in ~3-5 minutes
- Push Images to ACR: Should complete in ~2-3 minutes
- Deploy to Container Apps: Should complete in ~2-3 minutes
- View detailed logs by clicking on any stage or job
View Pipeline Run History
- View the pipeline run history:
- Go to Pipelines > Pipelines
- Click on your pipeline
- View the list of runs with status, duration, and trigger information
Note: You have successfully set up a CI/CD pipeline that automatically builds and deploys your Container Apps when code is pushed to the main branch.
Verify the Deployment
- After the pipeline completes, verify the deployment:
-
Check the Container Apps are running
# Check all Container Apps are running with the new images az containerapp list -g $RG ` --query "[].{Name:name, Image:properties.template.containers[0].image}" -o table -
Check the Container Apps Jobs are running
# Check the Jobs az containerapp job list -g $RG ` --query "[].{Name:name, Image:properties.template.containers[0].image}" -o table
-
Note that the image tags now include the Azure DevOps Build ID
- From the Azure Portal, navigate across the different Container Apps and see the latest revision reflecting the ADO pushed container images as Active Revision.
- You can still see and use the older one by selecting the Inactive revisions tab.
- Select Activate next to one of the inactive revision containers to present that version again as active revision.
- Switch from the Inactive Revision tab back to the Active Revision tab. Notice the container is in a Stopped state.
- Save the changes to start up the switched revision of the container again.
Task 2: Enhance the Pipeline with Traffic Splitting (Optional)
In this optional task, you will extend the pipeline to implement blue-green deployments with traffic splitting, automating what you did manually in Exercise 2, Task 2.
Create a Traffic Splitting Pipeline Stage
- Add a new stage to the pipeline for blue-green deployments. Copy the following YAML and add it after the
DeployAppsstage in yourazure-pipelines.yml:
Note: Remember to modify the pool syntax accordingly for your ADO Agent pool setup
```yaml
# ============================================================================
# Stage 4: Blue-Green Deployment (Optional - Manual Trigger)
# ============================================================================
- stage: BlueGreenDeploy
displayName: 'Blue-Green Deployment'
dependsOn: DeployApps
condition: and(succeeded(), eq(variables['deployBlueGreen'], 'true'))
variables:
containerRegistry: $[ stageDependencies.PushImages.PushToACR.outputs['pushImages.containerRegistry'] ]
resourceGroup: $[ stageDependencies.PushImages.PushToACR.outputs['pushImages.resourceGroup'] ]
jobs:
- deployment: BlueGreenHelloApi
displayName: 'Blue-Green Hello API'
pool:
vmImage: 'ubuntu-latest'
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: 'Enable Multiple Revision Mode'
inputs:
azureSubscription: $(azureSubscription)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az containerapp revision set-mode \
--name hello-api \
--resource-group $(resourceGroup) \
--mode Multiple
- task: AzureCLI@2
displayName: 'Deploy New Revision'
inputs:
azureSubscription: $(azureSubscription)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Deploy new revision with v2 suffix
az containerapp update \
--name hello-api \
--resource-group $(resourceGroup) \
--image $(containerRegistry)/hello-api:$(imageTag) \
--revision-suffix "v2-$(Build.BuildId)" \
--set-env-vars "APP_VERSION=v2"
- task: AzureCLI@2
displayName: 'Configure 50/50 Traffic Split'
inputs:
azureSubscription: $(azureSubscription)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Get the previous revision (v1)
V1_REVISION=$(az containerapp revision list \
--name hello-api \
--resource-group $(resourceGroup) \
--query "[?properties.active && !contains(name, 'v2-')].name | [0]" -o tsv)
# Get the new revision (v2)
V2_REVISION=$(az containerapp revision list \
--name hello-api \
--resource-group $(resourceGroup) \
--query "[?contains(name, 'v2-$(Build.BuildId)')].name | [0]" -o tsv)
echo "V1 Revision: $V1_REVISION"
echo "V2 Revision: $V2_REVISION"
# Split traffic 50/50
az containerapp ingress traffic set \
--name hello-api \
--resource-group $(resourceGroup) \
--revision-weight "$V1_REVISION=50" "$V2_REVISION=50"
echo "Traffic split configured: 50% v1, 50% v2"
```
-
Commit and push the updated pipeline:
git add azure-pipelines.yml git commit -m "Add blue-green deployment stage" git push
Add Pipeline Variable for Blue-Green Toggle
- To enable the blue-green stage, add a pipeline variable:
- Go to Pipelines > Pipelines
- Click on your pipeline
- Click Edit
- Click Variables (top right)
- Click New variable
- Name:
deployBlueGreen - Value:
true - Click OK and Save
Manual Traffic Promotion
-
After testing the 50/50 split, you can promote v2 to 100% traffic via pipeline or manually:
# Promote v2 to 100% traffic $V2_REVISION = az containerapp revision list -n hello-api -g $RG ` --query "[?contains(name, 'v2-')].name | [0]" -o tsvaz containerapp ingress traffic set ` --name hello-api ` --resource-group $RG ` --revision-weight "${V2_REVISION}=100" Write-Host "V2 promoted to 100% traffic"
Pipeline Triggers
-
Review the pipeline triggers in
azure-pipelines.yml:trigger: branches: include: - main - develop paths: include: - src/** - infra/** -
This configuration means:
- Pipeline runs automatically on pushes to
mainordevelop - Only triggers when files in
src/orinfra/change - Pull requests to
maintrigger validation builds
- Pipeline runs automatically on pushes to
Test Continuous Integration
-
Make a small change to trigger the pipeline:
# Make a change to the hello-api $content = Get-Content ".\src\hello-api\Program.cs" $content = $content -replace 'Contoso Analytics API', 'Contoso Analytics API v2' $content | Set-Content ".\src\hello-api\Program.cs"# Commit and push git add . git commit -m "Update API title - trigger CI/CD" git push -
Watch the pipeline automatically trigger
-
Verify the deployment completed:
# Check the hello-api version Invoke-RestMethod -Uri "$HELLO_API_URL/api/version"Note: You have successfully implemented CI/CD with Azure DevOps Pipelines, including automated builds, container registry pushes, and Container Apps deployments.
Clean up
Now that you’ve finished the exercise, you should delete the cloud resources you’ve created to avoid unnecessary resource usage. Choose the cleanup method that matches how you deployed.
Option A: Clean up with Azure CLI
Use this option if you deployed using Option A (Azure CLI + Bicep) in Exercise 1.
-
Delete the resource group and all resources within it:
# Delete the resource group and all resources within it az group delete --name $RG --yes --no-wait Write-Host "Resource group deletion initiated. This may take a few minutes." -
Verify the deletion:
# Check if resource group still exists az group exists --name $RG
Option B: Clean up with Azure Developer CLI (azd)
Use this option if you deployed using Option B (azd) in Exercise 1.
-
Run the azd down command to delete all resources:
# Delete all Azure resources provisioned by azd azd down --force --purge Write-Host "All resources have been deleted."Note: The
--forceflag skips confirmation prompts, and--purgepermanently deletes soft-deleted resources like Key Vault secrets.
Summary
In this exercise, you learned how to:
| Skill | What You Did |
|---|---|
| Infrastructure Deployment | Deployed Azure Container Apps infrastructure using Bicep templates via Azure CLI |
| Container Management | Built, pushed, and deployed container images to Azure Container Registry |
| Auto-Scaling | Observed HTTP-based auto-scaling under load with no additional configuration |
| Traffic Splitting | Implemented blue-green deployments with percentage-based traffic routing |
| Container Jobs | Worked with scheduled, manual, and parallel batch processing jobs |
| Security | Used Managed Identity and Key Vault for secure secret management |
| CI/CD Pipelines | Set up Azure DevOps Pipelines for automated build and deployment of Container Apps |
Key Takeaways
- Azure Container Apps provides a serverless container platform without Kubernetes complexity
- Auto-scaling is built-in and requires only simple threshold configuration
- Traffic splitting enables zero-downtime deployments and A/B testing
- Container Jobs replace Kubernetes CronJobs with simpler configuration
- Managed Identity provides secure, passwordless authentication to Azure services
- Azure DevOps Pipelines automate the entire CI/CD workflow from code commit to production deployment