Wednesday, February 4, 2026

Automating Azure Container Apps Deployment with PowerShell: A Complete CI/CD Guide

Automating Azure Container Apps Deployment with PowerShell: A Complete CI/CD Guide

Deploying containerized apps to the cloud sounds simple… until you’re juggling Docker builds, Azure registries, environments, networking, and deployment configs at 11 PM before a release 😅

That’s exactly where automation saves you.

In this guide, you’ll learn how to use a PowerShell deployment script that turns Azure Container Apps deployment into a single-command workflow — handling build, test, push, and deployment with minimal manual effort.

Whether this is your first cloud deployment or your hundredth, this setup keeps things fast, repeatable, and reliable.


What is Azure Container Apps?

Azure Container Apps (ACA) is a fully managed, serverless container platform. You run containers — Azure handles infrastructure.

It’s ideal for:

  • Web apps

  • APIs

  • Microservices

  • Background jobs

Why developers love it:

  • No server management
  • Auto-scaling based on demand
  • HTTPS out of the box
  • Pay-per-use pricing

You focus on code. Azure handles the rest.


What This Deployment Script Does

Instead of manually running 10+ commands, this script automates the entire pipeline:

  1. Environment validation (Azure CLI, Docker, login checks)

  2. Docker image build

  3. Optional local container testing

  4. Azure Container Registry (ACR) setup

  5. Image push to ACR

  6. Container Apps environment setup

  7. Deployment or update of your app

It works for first-time deployments and updates to existing apps.


Prerequisites

Before running the script, make sure you have:

ToolPurpose
Azure SubscriptionTo deploy resources
Azure CLIResource management
Docker Desktop (Linux mode)Container builds
PowerShell 5.1+Script execution
Git (optional)Version control

Azure resources needed

  • Resource Group (you create this)

  • Azure Container Registry (ACR) (script can create)

  • Container Apps Environment (script can create)


First-Time Deployment Walkthrough

Step 1 — Create a Resource Group

az group create --name "my-resource-group" --location "eastus"

Step 2 — Choose an ACR Name

Your registry name must be:

  • 5–50 characters

  • Alphanumeric only

  • Globally unique


Step 3 — Run the Deployment Script

.\deploy-azure.ps1 `
  -ResourceGroup "my-resource-group" `
  -ContainerName "sample-react-vite-app" `
  -AcrName "myacr" `
  -Environment "production" `
  -Location "eastus"

Step 4 — Follow Prompts

The script will:

✔ Check Azure login
✔ Build Docker image
✔ Offer local testing
✔ Create/validate ACR
✔ Push image
✔ Deploy to Container Apps


Script Parameters Explained

ParameterRequiredDescription
ResourceGroupAzure Resource Group
ContainerNameName of Container App
AcrNameRegistry name (strict format)
Environmentdevelopment / staging / production
LocationAzure region
PortApp listening port
DnsNameCustom domain

What Happens Behind the Scenes

Build Stage (Multi-Stage Docker)

  1. Node builder compiles the React app

  2. Lightweight nginx image serves it

  3. Final image is small and optimized


Registry Management

  • Validates ACR naming rules

  • Creates registry if missing

  • Authenticates securely

  • Tags images:

    • production-latest

    • production-20260204-150138


Deployment Logic

  • Detects if app exists

  • Creates new OR updates existing

  • Automatically pulls ACR credentials


Troubleshooting Quick Fixes

ProblemFix
Docker build failsStart Docker Desktop
ACR login failsaz acr login --name your-acr
Invalid registry nameUse only alphanumeric
Deployment failedCheck resource group + permissions
Assets 404Rebuild React app

After Deployment

You’ll get:

  • App name

  • Image path

  • Deployment status

  • Public HTTPS URL

Open the URL — Azure handles the SSL certificate automatically 🔒


Monitoring & Scaling

View logs

az containerapp logs show --name app --resource-group rg --follow

Scale replicas

az containerapp update --name app --resource-group rg --min-replicas 1 --max-replicas 3

Best Practices

✔ Use environment-specific builds
✔ Test locally before cloud deploy
✔ Use meaningful container names
✔ Keep images small (multi-stage builds)
✔ Monitor logs post-deploy
✔ Re-run script for updates
✔ Use versioned image tags for rollback


Advanced Scenarios

  • Multi-environment deployments (dev/staging/prod)

  • Custom DNS setup

  • Multi-region deployment


Dockerfile Strategy

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build:${BUILD_MODE}

FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Result: small, fast, production-ready image.


Cost Tips

  • Start with minimal CPU/memory

  • Scale only when needed

  • Monitor usage

  • Use environment-specific settings


Conclusion

This PowerShell automation turns Azure deployment into:

  • Faster releases
  • Fewer manual errors
  • Repeatable process
  • More developer focus on code

From multi-step chaos → single-command deployment.


Next Steps

  1. Install prerequisites

  2. Create resource group

  3. Pick ACR name

  4. Run script

  5. Open app URL

  6. Set up monitoring


Happy deploying! 

Final Script:

# Azure Container Apps Deployment Script

# This script builds and deploys the Sample React Vite App to Azure Container Apps


param(

    [Parameter(Mandatory=$true)]

    [string]$ResourceGroup,

    

    [Parameter(Mandatory=$true)]

    [string]$ContainerName,

    

    [Parameter(Mandatory=$true)]

    [string]$AcrName,

    

    [Parameter(Mandatory=$false)]

    [ValidateSet("development", "staging", "production")]

    [string]$Environment = "production",

    

    [Parameter(Mandatory=$false)]

    [string]$Location = "eastus",

    

    [Parameter(Mandatory=$false)]

    [int]$Port = 80,

    

    [Parameter(Mandatory=$false)]

    [string]$DnsName = $null

)


# Set error action preference

$ErrorActionPreference = "Stop"


# Validate ACR name

if ($AcrName -notmatch '^[a-zA-Z0-9]{5,50}$') {

    Write-Host "ERROR: Invalid ACR name '$AcrName'" -ForegroundColor Red

    Write-Host "ACR names must:" -ForegroundColor Yellow

    Write-Host "  - Contain only alphanumeric characters (no hyphens, underscores, or special characters)" -ForegroundColor Yellow

    Write-Host "  - Be between 5 and 50 characters long" -ForegroundColor Yellow

    Write-Host "" -ForegroundColor Yellow

    Write-Host "Example valid names: myacr123, myacr, contoso2024" -ForegroundColor Cyan

    exit 1

}


Write-Host "========================================" -ForegroundColor Cyan

Write-Host "Azure Container Deployment Script" -ForegroundColor Cyan

Write-Host "========================================" -ForegroundColor Cyan

Write-Host ""


# Variables

$ImageName = "sample-react-vite-app"

$ImageTag = "$Environment-$(Get-Date -Format 'yyyyMMdd-HHmmss')"

$FullImageName = "$AcrName.azurecr.io/${ImageName}:${ImageTag}"

$LatestImageName = "$AcrName.azurecr.io/${ImageName}:${Environment}-latest"


# Step 1: Check Azure CLI installation

Write-Host "Step 1: Checking Azure CLI..." -ForegroundColor Yellow

$azCheck = Get-Command az -ErrorAction SilentlyContinue

if (-not $azCheck) {

    Write-Host "Azure CLI is not installed. Please install it from https://aka.ms/InstallAzureCLI" -ForegroundColor Red

    exit 1

}

Write-Host "Azure CLI is installed" -ForegroundColor Green


# Step 2: Check if logged in to Azure

Write-Host "`nStep 2: Checking Azure login..." -ForegroundColor Yellow

$account = az account show 2>$null

if (-not $account) {

    Write-Host "Not logged in to Azure. Logging in..." -ForegroundColor Yellow

    az login

} else {

    Write-Host "Already logged in to Azure" -ForegroundColor Green

}


# Step 3: Build Docker image

Write-Host "`nStep 3: Building Docker image..." -ForegroundColor Yellow

Write-Host "Image: $FullImageName" -ForegroundColor Cyan

docker build --build-arg BUILD_MODE=$Environment -t ${ImageName}:${ImageTag} -t ${ImageName}:${Environment}-latest .

if ($LASTEXITCODE -ne 0) {

    Write-Host "Docker build failed" -ForegroundColor Red

    exit 1

}

Write-Host "Docker image built successfully" -ForegroundColor Green


# Step 4: Test Docker image locally (optional)

Write-Host "`nStep 4: Testing Docker image locally..." -ForegroundColor Yellow

$testResponse = Read-Host "Do you want to test the image locally before deployment? (y/n)"

if ($testResponse -eq 'y') {

    Write-Host "Starting container on port 3000..." -ForegroundColor Cyan

    docker run -d -p 3000:80 --name ${ImageName}-test ${ImageName}:${ImageTag}

    Write-Host "Container started. Visit http://localhost:3000 to test" -ForegroundColor Green

    Write-Host "Press Enter to continue with deployment (this will stop the test container)..."

    Read-Host

    docker stop ${ImageName}-test

    docker rm ${ImageName}-test

}


# Step 5: Check and create Azure Container Registry if needed

Write-Host "`nStep 5: Checking Azure Container Registry..." -ForegroundColor Yellow

$acrExists = az acr show --name $AcrName --resource-group $ResourceGroup 2>$null

if (-not $acrExists) {

    Write-Host "ACR '$AcrName' does not exist. Creating..." -ForegroundColor Yellow

    az acr create --name $AcrName --resource-group $ResourceGroup --sku Basic --location $Location

    if ($LASTEXITCODE -ne 0) {

        Write-Host "Failed to create ACR" -ForegroundColor Red

        exit 1

    }

    Write-Host "ACR created successfully" -ForegroundColor Green

} else {

    Write-Host "ACR '$AcrName' exists" -ForegroundColor Green

}


# Step 6: Login to Azure Container Registry

Write-Host "`nStep 6: Logging in to Azure Container Registry..." -ForegroundColor Yellow

az acr login --name $AcrName

if ($LASTEXITCODE -ne 0) {

    Write-Host "ACR login failed. Ensure Docker Desktop is running and you have permissions to the ACR." -ForegroundColor Red

    exit 1

}

Write-Host "Logged in to ACR" -ForegroundColor Green


# Step 7: Tag and push image to ACR

Write-Host "`nStep 7: Pushing image to Azure Container Registry..." -ForegroundColor Yellow

docker tag ${ImageName}:${ImageTag} $FullImageName

docker tag ${ImageName}:${ImageTag} $LatestImageName

docker push $FullImageName

docker push $LatestImageName

if ($LASTEXITCODE -ne 0) {

    Write-Host "Docker push failed" -ForegroundColor Red

    exit 1

}

Write-Host "Image pushed to ACR" -ForegroundColor Green


# Step 7: Create or update Azure Container Apps Environment

Write-Host "`nStep 8: Setting up Container Apps Environment..." -ForegroundColor Yellow


$EnvName = "commonservices-app-env"


# Check if environment exists

Write-Host "Checking if Container Apps Environment exists..." -ForegroundColor Cyan

$null = az containerapp env show --name $EnvName --resource-group $ResourceGroup 2>$null

if ($LASTEXITCODE -ne 0) {

    Write-Host "Creating Container Apps Environment..." -ForegroundColor Cyan

    az containerapp env create `

        --name $EnvName `

        --resource-group $ResourceGroup `

        --location $Location

    

    if ($LASTEXITCODE -ne 0) {

        Write-Host "Failed to create Container Apps Environment" -ForegroundColor Red

        exit 1

    }

    Write-Host "Container Apps Environment created" -ForegroundColor Green

} else {

    Write-Host "Container Apps Environment already exists" -ForegroundColor Green

}


# Step 8: Deploy to Azure Container Apps

Write-Host "`nStep 9: Deploying to Azure Container Apps..." -ForegroundColor Yellow


# Get ACR credentials

$acrPassword = az acr credential show --name $AcrName --query "passwords[0].value" -o tsv


# Check if container app exists

Write-Host "Checking if Container App exists..." -ForegroundColor Cyan

$appListJson = az containerapp list --resource-group $ResourceGroup --output json 2>$null

if ($appListJson) {

    $apps = $appListJson | ConvertFrom-Json

    $existingApp = $apps | Where-Object { $_.name -eq $ContainerName }

} else {

    $existingApp = $null

}


if ($existingApp) {

    Write-Host "Container App exists. Updating..." -ForegroundColor Cyan

    

    # Update registry credentials first

    az containerapp registry set `

        --name $ContainerName `

        --resource-group $ResourceGroup `

        --server "$AcrName.azurecr.io" `

        --username $AcrName `

        --password $acrPassword

    

    # Then update the image

    az containerapp update `

        --name $ContainerName `

        --resource-group $ResourceGroup `

        --image $FullImageName

} else {

    Write-Host "Container App does not exist. Creating new..." -ForegroundColor Cyan

    az containerapp create `

        --name $ContainerName `

        --resource-group $ResourceGroup `

        --environment $EnvName `

        --image $FullImageName `

        --target-port 80 `

        --ingress external `

        --registry-server "$AcrName.azurecr.io" `

        --registry-username $AcrName `

        --registry-password $acrPassword `

        --cpu 0.5 `

        --memory 1.0Gi

}


if ($LASTEXITCODE -ne 0) {

    Write-Host "Container App deployment failed" -ForegroundColor Red

    exit 1

}


Write-Host "Container App deployed successfully" -ForegroundColor Green


# Step 9: Get container app details

Write-Host "`nStep 10: Retrieving container app details..." -ForegroundColor Yellow

$appInfo = az containerapp show --name $ContainerName --resource-group $ResourceGroup --output json | ConvertFrom-Json


Write-Host "`n========================================" -ForegroundColor Cyan

Write-Host "Deployment Complete!" -ForegroundColor Green

Write-Host "========================================" -ForegroundColor Cyan

Write-Host "Container App Name: $ContainerName" -ForegroundColor White

Write-Host "Image: $FullImageName" -ForegroundColor White

Write-Host "Status: $($appInfo.properties.provisioningState)" -ForegroundColor White

Write-Host "HTTPS URL: https://$($appInfo.properties.configuration.ingress.fqdn)" -ForegroundColor Yellow

Write-Host ""

Write-Host "Note: Container Apps provides automatic HTTPS!" -ForegroundColor Green


Write-Host "`nUseful commands:" -ForegroundColor Cyan

Write-Host "  View logs: az containerapp logs show --name $ContainerName --resource-group $ResourceGroup --follow" -ForegroundColor Gray

Write-Host "  Scale: az containerapp update --name $ContainerName --resource-group $ResourceGroup --min-replicas 1 --max-replicas 3" -ForegroundColor Gray

Write-Host "  Delete: az containerapp delete --name $ContainerName --resource-group $ResourceGroup --yes" -ForegroundColor Gray

Write-Host "  Restart: az containerapp revision restart --name $ContainerName --resource-group $ResourceGroup" -ForegroundColor Gray

Write-Host ""


Monday, January 30, 2023

Export outputs into a csv in PowerShell

Recently I needed to perform some basic cleanup and syncing operation in one of the SharePoint online site. In process I also need to export differnces or changes I have made to keep in a CSV. I have not found any specific documentation about doing so in PowerShell. I have put a sample script here to find such details and generate a CSV. Here I have used connection to a SharePoint list.

#Setup Credentials to connect

Write-Host -ForegroundColor Yellow "Starting utility to sync data on instance lists from template list..." (get-date).ToString('T')

$siteUrl = "https://yourserver/sites/ListABC"

$instanceListFields = "Id","Title", "Description"

$applicationRegsitryList = "List For All List Titles";

$applicationRegsitryListFields = "Id","Title"

Write-Host -ForegroundColor Yellow "Connecting to Sharepoint online services to site... " $siteUrl

Connect-PnPOnline -Url $siteUrl -UsewebLogin

Write-Host -ForegroundColor Yellow "Connected to Sharepoint online services successfully."

New-Object -TypeName System.Collections.ArrayList

$finalMessage = [System.Collections.Arraylist]@()

$clientContext = Get-PnPContext

$targetWeb = Get-PnPWeb

$countOfListsUpdated = 0;

foreach($appListItem in $applicationRegsitryList)

{  

                    try {

                        $instanceListItems = Get-PnPListItem -List $appListItem["Title"] -Fields $instanceListFields -ErrorAction Stop

                        Write-Host "Got list : "$appListItem["Title"]" Item count: "$instanceListItems.Count

                        $actionStatus = "Got list : " + $appListItem["Title"] + " Item count: " + $instanceListItems.Count

                        $message = [System.Collections.Arraylist]@()

                        $message.Add("Found");

                        $message.Add($appListItem["Title"]);

                        $message.Add($actionStatus);

                        $message.Add($appListItem["ID"]);

                        $finalMessage.Add($message);

                        }

                    catch {

                        Write-Host -ForegroundColor Red "Exception Occurred... while fetching list from title"

                        $errormsg = $_.ToString();

                        $exception = $_.Exception;

                        $actionStatus = "Could not fetch list with Title " + $errormsg + " " +  $exception;

                        $message = [System.Collections.Arraylist]@()

                        $message.Add("Not Found");

                        $message.Add($appListItem["Title"]);

                        $message.Add($actionStatus);

                        $message.Add($appListItem["ID"]);

                        $finalMessage.Add($message);

                    }

}

$holder = @()

$pName = @("Status", "ListName", "Message", "ID")

$num = 4

FOREACH($row IN $finalMessage){

    $obj = New-Object PSObject

    for($i=0;$i -lt $num ; $i++){

        $obj | Add-Member -MemberType NoteProperty -name $pName[$i] -Value $row[$i]

    }

    $holder += $obj

    $obj = $null

}

$currTime = Get-Date -UFormat %R; 

$excelFileName =  "DiffChecker_" + (get-date).ToString('d_M_y') + "_" + $currTime.Replace(":", "") + ".csv"
$holder | Export-Csv $excelFileName -NoTypeInformation
Write-Host -ForegroundColor Yellow "Completed utility to find list for each title. Total lists processed: " $countOfListsUpdated " " (get-date).ToString('T')

Monday, February 15, 2021

Logic App to send emails.

 Working on one of my project I got a requirement regarding sending emails whenever a record moved from one status to another status. After going through multiple things we decided to use logic apps and send grid APIs to send emails. We have used a very straight forward approach to get it. Simply created a a new trigger on this table to capture change on status column and then through logic app started processing it on every 5 minutes. Below I will talk about, how to setup a logic app. 

Step 1: Search for logic app in https://portal.azure.com  search bar. Create a logic app:



Step 2: Provide details as requested and click on create. Once created successfully you will find below details:

 


Step 3: Select recurrence as it’s a time based logic app to check for status and sending emails.

 


Step4: Set recurrence interval, in this case keep it 5 mins.

 

 


Step 5: Create a connection with SQL server and select your db. Also need to provide db admin details.

 


 

Step 6:  Select server name, db name and procedure.

 


Step 7: Select another procedure, which process our send email table and return rows we need to send emails.






Step 8: Search send grid and select an action as Send Email.



Step 9: As soon as you would select to ResultsTo from dynamic contents in Send email action to field. A for each loop will be auto inserted as shown in step 8.

 




Step 10: Press save and click on run.





Step 11: Go to overview section and check its executing successfully as expected.