The image from part 6 runs anywhere containers run. This part puts it on Azure — choosing the right compute service, pushing to a container registry, wiring configuration and secrets properly, and the cost/operations trade-offs the pricing page won't tell you. (The same decisions map almost one-to-one onto AWS and GCP; the service names change, the shape doesn't.)

Choosing the compute service
Azure offers at least five ways to run a container. For a typical API, the honest decision tree:
- Azure App Service (Web App for Containers) — PaaS with the least ceremony: TLS, custom domains, deployment slots, easy scaling rules. Right default for "one API, steady traffic".
- Azure Container Apps — serverless containers on managed Kubernetes underneath: scale-to-zero, KEDA-based event scaling, Dapr integration. Right default for "several services, spiky traffic" — and where microservices (part 9) most comfortably land.
- AKS — real Kubernetes. Choose it when you need its ecosystem and have (or are) a platform team; you're signing up to operate a cluster.
- A plain VM with Docker Compose — unfashionable and underrated for small workloads: geeksarray.com runs on exactly this (a $12/month droplet-class VM, Compose, Caddy for TLS) for a fraction of PaaS pricing. The trade is that patching, backups, and TLS renewals are your job.
This part deploys to Container Apps — it fits the series' trajectory.
Step 1: registry
az group create --name roadmap-rg --location centralindia
az acr create --resource-group roadmap-rg --name roadmapacr --sku Basic
az acr build --registry roadmapacr --image roadmap-api:v1 .
az acr build uploads the build context and runs your Dockerfile in Azure — no local Docker needed, and the image lands in the registry in one step. (It honors .dockerignore, which part 6 taught us to write early.)
Step 2: the app
az containerapp env create --name roadmap-env --resource-group roadmap-rg --location centralindia
az containerapp create \
--name roadmap-api --resource-group roadmap-rg --environment roadmap-env \
--image roadmapacr.azurecr.io/roadmap-api:v1 \
--registry-server roadmapacr.azurecr.io --registry-identity system \
--target-port 8080 --ingress external \
--min-replicas 0 --max-replicas 3 \
--secrets jwt-key=<your-32-char-secret> \
--env-vars Jwt__Key=secretref:jwt-key ConnectionStrings__Default="<db-connection-string>"
The pieces that matter:
--target-port 8080matches theASPNETCORE_URLSwe baked into the image. Ingress gives you HTTPS on a generated hostname immediately; bind a custom domain when ready.--min-replicas 0is scale-to-zero: a dev/staging app that costs nothing at night. For production APIs set--min-replicas 1+— cold starts are real.- Secrets vs. env vars.
Jwt__Keyarrives as a secret reference, not a plain env var in your deployment scripts. The next step up is Azure Key Vault with managed identity, which removes the secret from the CLI command too. - Managed identity for the registry (
--registry-identity system) — no registry passwords to store or rotate.
Step 3: the database
The SQLite file dies with the container — fine for the demo, wrong for production. Swap the connection string for Azure Database for PostgreSQL Flexible Server (or Azure SQL). Because part 1 isolated persistence behind ports and part 2 kept the provider choice in one line (UseSqlite → UseNpgsql), this is an infrastructure-only change. Start with the burstable tier (~₹1,200/month); you can scale a database up much more easily than you can un-choose a serverless pricing model.
Configuration recap
The image never changes between environments. Everything environment-specific flows in as env vars/secrets, exactly as in part 6's local run:
ConnectionStrings__Default → managed database
Jwt__Key → Key Vault / Container Apps secret
ASPNETCORE_ENVIRONMENT → Production
If you catch yourself building a -prod image variant, stop — that's configuration trying to sneak into the artifact.
Operations you should turn on before traffic
- Log streaming:
az containerapp logs show --followfor the quick look; wire Log Analytics for retention. - Health probes: point liveness/readiness at a cheap endpoint so the platform restarts a wedged replica.
- Revisions: Container Apps keeps immutable revisions per deploy —
az containerapp revision list— giving you instant rollback (--revisiontraffic splitting even enables canaries). - Budget alert:
az consumption budget create ...— a ₹2,000 alert email beats a surprise invoice. Cloud bills fail silently in the expensive direction.
Scaling rules that match how APIs actually load
Container Apps scales on KEDA rules, and the defaults are HTTP-centric:
az containerapp update --name roadmap-api --resource-group roadmap-rg \
--scale-rule-name http-load --scale-rule-type http \
--scale-rule-http-concurrency 60 \
--min-replicas 1 --max-replicas 5
Sixty concurrent requests per replica is a sane starting point for an I/O-bound API; watch your p95 latency and tune. Two traps:
- Scaling on CPU for an I/O-bound API. A .NET API waiting on the database shows 8% CPU while queueing requests — CPU rules never fire. Concurrency (or queue-depth, for workers) reflects real pressure.
- Min replicas 0 in production. Scale-to-zero saves ~₹800/month and costs you a multi-second cold start on the request that matters — usually the demo to a client. Keep 1 warm replica for anything user-facing; save zero for staging.
And remember the database is the actual ceiling: five API replicas hammering a burstable Postgres just moves the queue. Scale reads with part 5's caching before scaling compute.
Custom domain and TLS
The generated *.azurecontainerapps.io hostname is fine for testing; production wants your domain:
az containerapp hostname add --hostname api.geeksarray.com \
--name roadmap-api --resource-group roadmap-rg
az containerapp hostname bind --hostname api.geeksarray.com \
--name roadmap-api --resource-group roadmap-rg --validation-method CNAME
You add the CNAME (and a TXT validation record) at your DNS provider, and Azure provisions and renews the certificate — the same "TLS becomes someone else's job" trade that Caddy gives you on a VM. The verification dance takes minutes; the certificate renewal you never think about again is the point.
What this actually costs
Rough monthly reality for this stack in Central India, small-but-real traffic:
| Piece | Tier | ~Cost |
|---|---|---|
| Container Apps | 1 warm replica, 0.5 vCPU/1 GiB | ₹1,100–1,600 |
| PostgreSQL Flexible | Burstable B1ms | ₹1,200–1,500 |
| Container Registry | Basic | ₹400 |
| Log Analytics | first 5 GB free | ₹0–500 |
Call it ₹3,000–4,000/month — versus ~₹1,000 for the VM-plus-Compose shape. What the extra buys: managed TLS and scaling, revisions with instant rollback, a database with automated backups/patching, and no 2 a.m. kernel updates. Whether that trade is worth 3× is a business question; knowing both shapes (this series has now built each) means you're choosing, not defaulting.
Infrastructure as code
Every command above is a one-off. The moment the environment works, capture it — Bicep is the natural Azure choice:
resource api 'Microsoft.App/containerApps@2024-03-01' = {
name: 'roadmap-api'
properties: {
configuration: { ingress: { external: true, targetPort: 8080 } }
template: {
containers: [{ name: 'api', image: '${acr.properties.loginServer}/roadmap-api:v1' }]
scale: { minReplicas: 1, maxReplicas: 3 }
}
}
}
az deployment group create --template-file main.bicep rebuilds the environment from nothing — which turns disaster recovery from a runbook into a command.
Next
Manual az acr build + az containerapp update works exactly until the second team member joins. Part 8 automates the whole path — build, test, image, deploy — with GitHub Actions, so the only deployment command left is git push.
Comments (0)
No comments yet — be the first to share your thoughts.