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
- Make the app bind to 0.0.0.0 and use a configurable port. Example properties for Spring Boot:
server.port=5000
server.address=0.0.0.0- Add a health endpoint (eg. <code>/health</code> or <code>/ready</code>) returning 200 when ready. This helps load balancers and diagnostics.
- 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):
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):
docker build -t myapp:local .
docker run -e PORT=5000 -p 5000:5000 myapp:localImportant 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):
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):
# 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:v1Deploying 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:
az provider register --namespace Microsoft.App --wait
az provider register --namespace Microsoft.OperationalInsights --wait2) Create an ACR and enable admin (or use a service principal)
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 myregistryFor 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
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:
az acr build --registry myregistry --image myapp:v1 .4) Create a Container Apps environment
az containerapp env create \
--name app-env \
--resource-group my-rg \
--location centralindiaThis environment wires logging, Dapr support, networking and scaling primitives for Container Apps.
5) Deploy the container
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'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'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'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:
az group create --name my-rg --location centralindiaAssign <code>acrpull</code> role to a managed identity (example):
# 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 / Need | AWS | Azure | Notes |
|---|---|---|---|
| Virtual servers | EC2 | Virtual Machines | Same idea: full OS control |
| Managed containers | ECS / EKS | Container Apps / AKS | Container Apps is serverless-focused; AKS is managed k8s |
| Container registry | ECR | ACR | Both host images; ACR integrates with Azure RBAC and Managed Identities |
| Serverless functions | Lambda | Functions | Similar FaaS concepts |
| Object storage | S3 | Blob Storage | Feature parity for most use-cases |
| Managed relational DB | RDS | Azure Database for PostgreSQL / Azure SQL | Both provide managed engines |
| NoSQL | DynamoDB | Cosmos DB | Cosmos DB is multi-model and globally distributed |
| Caching | ElastiCache | Azure Cache for Redis | Managed Redis |
| IAM | IAM (users, roles, policies) | Azure AD + RBAC | Azure AD is central identity; RBAC is authorization model |
| Monitoring | CloudWatch | Application Insights + Monitor + Log Analytics | Azure splits telemetry into specialized services |
| Infra-as-code | CloudFormation / CDK | ARM / Bicep / Terraform | Bicep is Azure's concise DSL over ARM |
| Global traffic routing | Route53 + CloudFront | Traffic Manager + Front Door + CDN | Front Door offers global HTTP routing + WAF |
| Private network | VPC | VNet | Similar networking primitives |
| Secret store | Secrets Manager / SSM | Key Vault | Key Vault integrates with Managed Identity |
Use this table as a quick lookup while you'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
- Open the backend URL directly (bypass CDN) to confirm it responds.
- Check container logs:
az containerapp logs show --name backend-app --resource-group my-rg- Check app health endpoint and readiness.
- Confirm frontend is calling the right base URL (not localhost or old AWS URL).
- 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:
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.