Featured

From AWS to Azure: A detailed, practical guide to learn Cloud + DevOps by deploying a real app

A hands-on walkthrough to move one real backend project from AWS to Azure - with Docker, CLI commands, and troubleshooting steps.

May 28, 202610 min read

Goal: learn by moving one real app

This guide assumes you have one small working service locally (a Spring Boot API, Express/Node service, or similar). The learning path is:

  • Build and run locally.
  • Deploy to AWS to understand the classic cloud model.
  • Rebuild the same deployment on Azure and fix migration issues.

By repeating the same app across clouds you learn the common abstractions (networking, config, images, auth), not just vendor docs.

Keep the project small

  • One API service
  • Optional one database (managed or self-hosted)
  • A single pub lic URL

That minimal surface lets you focus on deployment flow and debugging rather than feature complexity. <strong>Prerequisites</strong>

  • <code>docker</code> and <code>docker buildx</code> configured
  • <code>az</code> (Azure CLI) installed and logged in
  • <code>aws</code> CLI installed and configured (optional if you already deployed on AWS)
  • A small app listening on a configurable port (eg. 5000)

Step A - Prepare the app for cloud

  1. Make the app bind to 0.0.0.0 and use a configurable port. Example properties for Spring Boot:
properties
server.port=5000
server.address=0.0.0.0
  1. Add a health endpoint (eg. <code>/health</code> or <code>/ready</code>) returning 200 when ready. This helps load balancers and diagnostics.
  1. Externalize configuration (env vars) for database URLs, secrets, and API base URLs. Never hardcode production secrets.

Step B - Dockerize the app (the universal bridge)

Create a small Dockerfile (example for a Node app):

dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
ENV PORT=5000
EXPOSE 5000
CMD ["node","index.js"]

For Java / Spring Boot use a multistage build with <code>maven</code>/<code>gradle</code> to produce a thin runtime image. Build locally (simple):

bash
docker build -t myapp:local .
docker run -e PORT=5000 -p 5000:5000 myapp:local

Important cross-platform note: on Apple Silicon (M1/M2) Docker may build ARM images by default. To run in many cloud runtimes you need linux/amd64 images (see buildx below).

Step C - Build a cloud-compatible image

Use <code>docker buildx</code> to produce images for linux/amd64 and push to registries. Example - build + push to a registry (generic):

bash
docker buildx build --platform linux/amd64 -t registry.example.com/myapp:v1 --push .

Why: Many cloud container platforms (ACR, ECR, Google Cloud Run) expect a linux/amd64 image. The common migration error is <code>no child with platform linux/amd64</code> - a signal you built the wrong architecture.

Quick AWS recap (deploy once to learn the flow)

Purpose: see how images, networking, and permissions work in a classic cloud.

  • Push image to ECR (AWS Container Registry).
  • Run on ECS or Elastic Beanstalk, or even on EC2 to see raw server management.

High-level AWS CLI ECR commands (abbreviated):

bash
# create repo
aws ecr create-repository --repository-name myapp
# login
aws ecr get-login-password | docker login --username AWS --password-stdin <aws_account>.dkr.ecr.<region>.amazonaws.com
# tag and push
docker tag myapp:local <aws_account>.dkr.ecr.<region>.amazonaws.com/myapp:v1
docker push <aws_account>.dkr.ecr.<region>.amazonaws.com/myapp:v1

Deploying on EC2 (manual) vs ECS (managed) is a good contrast: EC2 teaches server maintenance, ECS teaches container orchestration.

Step D - Moving to Azure: the common migration steps

Azure offers a few container options; a smooth, low-ops path is <strong>Azure Container Apps</strong> or <strong>Azure App Service for Containers</strong> plus <strong>Azure Container Registry (ACR)</strong>. Main migration areas:

  • Registry: ECR → ACR
  • Runtimes: ECS/EC2 → Container Apps / App Service
  • Logging: CloudWatch → Log Analytics

1) Ensure Azure resource providers are registered

If you see errors like <code>Microsoft.App not registered</code> or <code>Microsoft.OperationalInsights not registered</code>, run:

bash
az provider register --namespace Microsoft.App --wait
az provider register --namespace Microsoft.OperationalInsights --wait

2) Create an ACR and enable admin (or use a service principal)

bash
az acr create --name myregistry --resource-group my-rg --sku Standard --location centralindia
# optional: enable admin user (easier for demo, not recommended for prod)
az acr update --name myregistry --admin-enabled true
az acr credential show --name myregistry

For production use, create a service principal with <code>az ad sp create-for-rbac</code> and grant <code>acrpull</code> to the identity the runtime uses.

3) Build and push an ACR image

bash
docker buildx build --platform linux/amd64 -t myregistry.azurecr.io/myapp:v1 --push .

If you prefer to build remotely, Azure Container Registry supports <code>az acr build</code> which builds in the cloud and avoids local cross-build issues:

bash
az acr build --registry myregistry --image myapp:v1 .

4) Create a Container Apps environment

bash
az containerapp env create \
  --name app-env \
  --resource-group my-rg \
  --location centralindia

This environment wires logging, Dapr support, networking and scaling primitives for Container Apps.

5) Deploy the container

bash
az containerapp create \
  --name backend-app \
  --resource-group my-rg \
  --environment app-env \
  --image myregistry.azurecr.io/myapp:v1 \
  --target-port 5000 \
  --ingress external \
  --registry-server myregistry.azurecr.io \
  --registry-username <acr-username> \
  --registry-password <acr-password>

Notes:

  • Use a service principal or managed identity in production instead of embedding credentials.
  • <code>--target-port</code> must match the port your container exposes.

Azure concepts explained

Understanding Azure&#39;s key building blocks will help you map AWS knowledge to Azure terminology and capabilities.

  • <strong>Subscription</strong>: The top-level billing/administrative boundary. Subscriptions contain resource groups and resources.
  • <strong>Resource Group</strong>: A logical container for related resources (compute, storage, networking) that share lifecycle and permissions.
  • <strong>Azure Resource Manager (ARM) / Bicep</strong>: The deployment and management layer. ARM templates or Bicep files declare resources as code.
  • <strong>Azure Active Directory (Azure AD)</strong>: Identity provider for users, apps, and service principals. Use it for authentication and RBAC.
  • <strong>Managed Identity</strong>: A first-class identity tied to a resource (system-assigned or user-assigned) that can access other Azure resources without storing credentials.
  • <strong>Role-Based Access Control (RBAC)</strong>: Azure&#39;s authorization model. Assign roles (Reader, Contributor, AcrPull) to identities at scope (subscription, resource group, resource).
  • <strong>Azure Container Registry (ACR)</strong>: Private container registry similar to ECR.
  • <strong>Azure Container Apps</strong>: Serverless container hosting for microservices with autoscaling, HTTP ingress, Dapr integration, and managed infrastructure.
  • <strong>Azure App Service (for Containers)</strong>: PaaS host for web apps and containers with built-in auth, scaling, and deployment slots.
  • <strong>Azure Kubernetes Service (AKS)</strong>: Managed Kubernetes for full control over container orchestration.
  • <strong>Azure Functions</strong>: Serverless functions for event-driven workloads.
  • <strong>Azure Storage (Blob)</strong>: Object storage for files, similar to S3.
  • <strong>Azure SQL / Azure Database for PostgreSQL</strong>: Managed relational databases similar to RDS.
  • <strong>Cosmos DB</strong>: Globally distributed, multi-model database (NoSQL) with low latency.
  • <strong>Azure Cache for Redis</strong>: Managed Redis offering.
  • <strong>Application Insights / Azure Monitor / Log Analytics</strong>: Observability stack for metrics, traces, logs, and alerts (CloudWatch equivalent is split across these services).
  • <strong>Virtual Network (VNet)</strong>: Azure&#39;s private network boundary for resources; similar to VPC.
  • <strong>Application Gateway / Azure Front Door / Traffic Manager</strong>: Layer 7 load balancers, global routing, WAF, and CDN-like fronting services.
  • <strong>Private Link</strong>: Securely access PaaS resources privately over your VNet without exposing public endpoints.

These building blocks combine to form typical production architectures: a VNet for private connectivity, ACR for images, Container Apps or AKS for compute, Key Vault for secrets, managed DBs for storage, and Application Insights for telemetry.

Common Azure CLI snippets

Create a resource group:

bash
az group create --name my-rg --location centralindia

Assign <code>acrpull</code> role to a managed identity (example):

bash
# create a user-assigned managed identity
az identity create --name myIdentity --resource-group my-rg
# get the principal id
PRINCIPAL_ID=$(az identity show -g my-rg -n myIdentity --query principalId -o tsv)
# assign acrpull on the registry
az role assignment create --assignee $PRINCIPAL_ID --role "AcrPull" --scope $(az acr show -g my-rg -n myregistry --query id -o tsv)

When to choose which Azure compute option

  • <strong>Container Apps</strong>: quickest path from Docker image to managed HTTP service with autoscale and minimal infra management.
  • <strong>App Service (Containers)</strong>: if you want PaaS features like deployment slots, built-in auth, and easy scaling for web apps.
  • <strong>AKS</strong>: if you need Kubernetes features (custom networking, operators, complex deployments).
  • <strong>Functions</strong>: for event-driven, short-lived tasks; not ideal for long-running HTTP APIs.

AWS ↔ Azure comparison table

Concept / NeedAWSAzureNotes
Virtual serversEC2Virtual MachinesSame idea: full OS control
Managed containersECS / EKSContainer Apps / AKSContainer Apps is serverless-focused; AKS is managed k8s
Container registryECRACRBoth host images; ACR integrates with Azure RBAC and Managed Identities
Serverless functionsLambdaFunctionsSimilar FaaS concepts
Object storageS3Blob StorageFeature parity for most use-cases
Managed relational DBRDSAzure Database for PostgreSQL / Azure SQLBoth provide managed engines
NoSQLDynamoDBCosmos DBCosmos DB is multi-model and globally distributed
CachingElastiCacheAzure Cache for RedisManaged Redis
IAMIAM (users, roles, policies)Azure AD + RBACAzure AD is central identity; RBAC is authorization model
MonitoringCloudWatchApplication Insights + Monitor + Log AnalyticsAzure splits telemetry into specialized services
Infra-as-codeCloudFormation / CDKARM / Bicep / TerraformBicep is Azure&#39;s concise DSL over ARM
Global traffic routingRoute53 + CloudFrontTraffic Manager + Front Door + CDNFront Door offers global HTTP routing + WAF
Private networkVPCVNetSimilar networking primitives
Secret storeSecrets Manager / SSMKey VaultKey Vault integrates with Managed Identity

Use this table as a quick lookup while you&#39;re translating an architecture or team knowledge from AWS terms into Azure concepts.

Common migration errors and fixes

  • <code>no child with platform linux/amd64</code> — build with <code>--platform linux/amd64</code> or use <code>az acr build</code>.
  • <code>Microsoft.App not registered</code> — register providers (see step above).
  • Registry auth failures — enable admin temporarily, or use SP/managed identity with proper <code>acrpull</code> role.
  • 502 / Bad Gateway from CDN or proxy — check backend health, CORS, and the base URL configured in frontend.

502 troubleshooting checklist

  1. Open the backend URL directly (bypass CDN) to confirm it responds.
  2. Check container logs:
bash
az containerapp logs show --name backend-app --resource-group my-rg
  1. Check app health endpoint and readiness.
  2. Confirm frontend is calling the right base URL (not localhost or old AWS URL).
  3. Confirm TLS/HTTPS and any reverse proxy path prefixes are correct.

Debugging tips (practical)

  • Use <code>curl -v</code> against the container app to see redirects and TLS issues.
  • Temporarily set <code>LOG_LEVEL=debug</code> to get more verbose logs.
  • If the container starts but immediately exits, run it locally with same env vars to reproduce.
  • Use <code>az containerapp revision list</code> to see revisions and traffic allocation if you use progressive deployments.

Example: healthcheck + readiness pattern

Implement lightweight endpoints:

  • <code>GET /health</code> — returns 200 when process running
  • <code>GET /ready</code> — checks DB connections or external deps and returns 200 only when ready

Keep health checks fast and idempotent.

Security and credentials

  • Do not store credentials in images. Use environment variables or secret stores.
  • On Azure, prefer Managed Identities + Key Vault for production secret management. For Container Apps, mount secrets via <code>az containerapp secret set</code> and reference them as environment variables when creating the app.

Example set secret and use it:

bash
az containerapp secret set --name backend-app --resource-group my-rg --secrets DB_URL="postgres://..."
az containerapp update --name backend-app --resource-group my-rg --set-env-vars DB_URL="{{secrets.DB_URL}}"

Observability: logs and metrics

  • Azure: Log Analytics (linked automatically for Container Apps environments) — query logs in the portal.
  • Add structured logging (JSON) from your app so logs are searchable and parseable.
  • Add health metrics (request latencies, error rates) and alerts when thresholds are crossed.

Next steps and experiments

  • Add managed database (Azure Database for PostgreSQL / RDS equivalent) and migrate data.
  • Replace ACR admin creds with a Managed Identity and grant <code>acrpull</code>.
  • Add a CDN or custom domain with TLS and test redirects and HSTS.
  • Try the same flow on Google Cloud Run and compare differences.

Final checklist before calling migration done

  • App responds on direct container URL and <code>/health</code> is green
  • Image built for linux/amd64 and available in ACR
  • Container App runs and logs are visible
  • Frontend points to new Azure URL and CORS works
  • Secrets moved to secret store and not embedded in images

--- This guide is intentionally practical and compact: use it as a checklist while you move one real app across clouds. Want a copy with concrete, runnable examples for a specific stack (Spring Boot, Node/Express, or FastAPI)? Tell me which stack and I will generate a ready-to-run repo and CI pipeline for that stack.