All lessons
DevOps & cloudIntermediate12 min readWorth knowing

Microsoft Azure

Microsoft's cloud: App Service, Container Apps, AKS and Azure SQL, tied together by Entra ID.

  • App Service
  • AKS
  • Entra ID
  • Azure SQL
Step 1/5
One VPS: cheap and simple, but you're also the admin, the backup guy and the 3 a.m. alarm clock.

In 30 seconds

Azure is Microsoft's public cloud. It offers the same building blocks as AWS and Google Cloud (virtual machines, managed databases, containers, serverless functions, storage), but its biggest strength is integration with the Microsoft world: Entra ID for identity, .NET, SQL Server, Office 365 and enterprise agreements. Resources are grouped into subscriptions and resource groups, and every action goes through Azure Resource Manager.

Not in my daily stack — I know the concepts and would learn the details when a project needs it.

Why it matters

  • Many enterprises already run on Microsoft: their users are in Entra ID, their licences are in an enterprise agreement, and their apps are .NET with SQL Server. Azure is the shortest path for them.
  • App Service and Container Apps let a team host web apps and containers without managing servers, with deployment slots, autoscaling and managed certificates.
  • Managed identities remove most secrets from application code, which is a real security improvement over passwords in config files.
  • For client work, knowing Azure matters because the choice of cloud is often made by the client's IT department, not by the developer.

How it works

01The mental model: tenant, subscriptions, resource groups

At the top is a tenant: your organisation's Entra ID directory, which holds users, groups and app registrations. Under it are subscriptions, which are billing and quota boundaries; a common pattern is one subscription for production and one for non-production. Management groups sit above subscriptions to apply policy to many at once. Inside a subscription, every resource lives in a resource group: a folder for things that share a lifecycle, like the app, its database and its Key Vault for one environment. Deleting a resource group deletes everything in it, which is very handy for cleaning up test environments. Every create, update and delete goes through Azure Resource Manager (ARM), whether you use the portal, the az CLI, Bicep or Terraform. Compared with AWS, a subscription is close to an AWS account and a resource group has no exact equivalent, it is closer to a tag-based grouping you can act on.

  • Tenant (Entra ID) → management groups → subscriptions → resource groups → resources.
  • One resource group per app per environment keeps clean-up and permissions simple.
  • Everything is an ARM resource with an ID, so it can be described as code.

02Hosting options: App Service, Container Apps, AKS, Functions

App Service is the classic platform for web apps: you choose a runtime (Node, PHP, .NET, Python, Java) or a container, and it handles the servers, TLS and scaling. You pay for an App Service plan, a set of VMs that can host several apps, so cost is fixed per plan size, not per request. Deployment slots let you deploy to a staging slot, warm it up and swap it into production with no downtime. Azure Container Apps runs containers serverlessly on top of Kubernetes without exposing it: autoscaling with KEDA rules (HTTP, queues, cron), scale to zero, revisions and traffic splitting. It is the closest equivalent to Google Cloud Run. AKS is full managed Kubernetes for teams that need the Kubernetes API. Azure Functions runs event-driven code; for new serverless apps Microsoft recommends the Flex Consumption plan, and the Linux Consumption plan is retiring in September 2028.

03Deploying a container to Azure Container Apps

A typical flow: create a resource group, an Azure Container Registry, and a Container Apps environment (the shared network and logging boundary for a group of apps). Build and push your image to the registry, then create the container app. Give the app a managed identity and grant it AcrPull on the registry so no registry password is needed. Ingress makes it reachable over HTTPS on a generated domain, and you can add a custom domain with a free managed certificate. Scale rules decide the minimum and maximum replicas; a minimum of zero is cheapest, a minimum of one avoids cold starts. Secrets can reference Key Vault directly, so the app reads them as environment variables without them ever living in your pipeline.

bash
az group create --name rg-app-prod --location westeurope
az acr create --resource-group rg-app-prod --name exampleappacr --sku Basic
az acr build --registry exampleappacr --image api:1.4.2 .
 
az containerapp env create --name cae-app-prod \
  --resource-group rg-app-prod --location westeurope
 
az containerapp create --name api \
  --resource-group rg-app-prod --environment cae-app-prod \
  --image exampleappacr.azurecr.io/api:1.4.2 \
  --registry-server exampleappacr.azurecr.io --registry-identity system \
  --target-port 3000 --ingress external \
  --min-replicas 0 --max-replicas 10

04Data: Azure SQL, PostgreSQL Flexible Server, Cosmos DB, Storage

Azure SQL Database is managed SQL Server in the cloud. You buy it in vCore or older DTU models; the serverless tier can auto-pause when idle and bill compute per second, which is great for dev databases. For open-source stacks, Azure Database for PostgreSQL Flexible Server and MySQL Flexible Server are managed instances with automated backups, zone-redundant high availability and stop/start for non-production. Cosmos DB is a globally distributed NoSQL database with several APIs (NoSQL, MongoDB, Cassandra); it is billed in request units, so a badly designed query or partition key costs real money. Azure Storage accounts hold blobs (files), queues, tables and file shares, with hot, cool, cold and archive tiers. Azure Cache for Redis is being succeeded by Azure Managed Redis, so check which one is current before starting a new project.

  • .NET + SQL Server → Azure SQL Database.
  • Laravel / Node + Postgres → PostgreSQL Flexible Server.
  • Global, low-latency NoSQL with predictable partitions → Cosmos DB.

05Identity: Entra ID, RBAC and managed identities

Entra ID (the new name for Azure Active Directory) is both the directory for your staff and the identity provider for your apps. It does single sign-on, MFA and conditional access for people, and OAuth 2.0 / OpenID Connect for applications through app registrations. Permissions on Azure resources use Azure RBAC: assign a role (Reader, Contributor, Storage Blob Data Reader…) to a principal at a scope (management group, subscription, resource group or single resource). The most useful feature for developers is managed identity: Azure gives your App Service, Container App or Function an identity that can get tokens automatically. Grant that identity a role on Key Vault, Storage or the database, and your code connects using DefaultAzureCredential with no password at all. For CI, GitHub Actions can log in with workload identity federation (OIDC) instead of a stored client secret.

ts
import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
 
// Works locally (az login) and in Azure (managed identity), no secrets in code
const credential = new DefaultAzureCredential();
const vault = new SecretClient("https://kv-app-prod.vault.azure.net", credential);
 
const secret = await vault.getSecret("stripe-api-key");
console.log(secret.name, "loaded");

06Production tips and pricing traps

Azure bills many things by the hour whether you use them or not. An App Service plan costs the same with zero apps or ten. A VM that is shut down from inside the OS still bills; you must deallocate it. Premium features can surprise you: Application Gateway, Azure Firewall, Front Door premium tiers, Bastion and NAT Gateway all have fixed hourly costs that can outgrow a small app. Log Analytics charges per GB ingested, so a chatty app with verbose logging can cost more in logs than in compute; set a daily cap and retention. Use Cost Management budgets and alerts from day one, tag resources with owner and environment, and put test environments in their own resource groups so they can be deleted in one command. Reservations and savings plans lower the price of steady compute by committing for one or three years.

Use it when

  • The organisation already uses Microsoft 365, Entra ID and enterprise agreements, so identity, billing and support are already in place.
  • The stack is .NET and SQL Server, where Azure tooling, licensing benefits and support are strongest.
  • You need enterprise features such as conditional access, private endpoints and policy-driven compliance across many subscriptions.
  • You want serverless containers (Container Apps) or managed web hosting (App Service) with deployment slots.

Skip it when

  • A small startup app with no Microsoft dependency: a VPS, Cloud Run or a simpler PaaS will be cheaper and faster to set up.
  • The team already has deep AWS or GCP experience and infrastructure; adding Azure only adds a second set of concepts.
  • You need very low, predictable monthly costs: Azure's fixed hourly components add up quickly for small projects.

Alternatives

  • AWS

    The broadest service catalogue. Elastic Beanstalk/App Runner map to App Service, ECS/EKS to Container Apps/AKS, RDS to Azure SQL and Flexible Server.

  • Google Cloud

    Cloud Run is a simpler serverless container platform; BigQuery is stronger for analytics.

  • VPS + Docker Compose

    Cheapest and simplest for steady traffic, but no managed identity, failover or compliance tooling.

  • Vercel / Netlify

    Better for frontend apps that need previews and a CDN, with the backend hosted elsewhere.

Best practices

  1. Use one resource group per app per environment and separate subscriptions for production and non-production.
  2. Use managed identities and DefaultAzureCredential instead of connection strings with passwords.
  3. Store secrets and certificates in Key Vault with RBAC, and reference them from App Service or Container Apps.
  4. Describe infrastructure in Bicep or Terraform; treat portal clicks as read-only exploration.
  5. Use deployment slots or Container Apps revisions for zero-downtime releases and fast rollback.
  6. Set budgets, tags and Log Analytics daily caps before the first production deploy.
  7. Apply Azure Policy for allowed regions, required tags and denying public network access on data services.

Common mistakes

  • Giving developers Owner on the whole subscription instead of Contributor on their resource groups.
  • Stopping a VM from inside the OS and assuming billing stopped; it must be deallocated.
  • Keeping SQL connection strings with passwords in app settings and source control when managed identity would work.
  • Choosing a Cosmos DB partition key that creates hot partitions and huge request-unit bills.
  • Starting new serverless functions on the legacy Consumption plan instead of Flex Consumption.

Deep dive

07Networking: VNets, private endpoints and the public-access trap

By default many Azure PaaS services (Storage, SQL, Key Vault, Cosmos DB) have public endpoints protected only by authentication and optional IP firewalls. For production data, the stronger pattern is private endpoints: the service gets a private IP inside your VNet, a private DNS zone resolves its normal hostname to that IP, and you disable public network access. Apps reach the VNet through VNet integration (App Service, Functions Flex and Premium) or by running the Container Apps environment inside a VNet. The common failure is DNS: if the privatelink zone is not linked to the VNet, the app still resolves the public IP and gets blocked. Hub-and-spoke topologies put shared services (firewall, VPN, DNS) in a hub VNet peered with app spokes. Each piece has a cost: private endpoints bill per hour and per GB, Azure Firewall is expensive for small projects.

bash
# Private endpoint for a PostgreSQL Flexible Server is set at creation;
# for Key Vault, deny public access and add a private endpoint:
az keyvault update --name kv-app-prod --public-network-access Disabled
 
az network private-endpoint create --name pe-kv-app-prod \
  --resource-group rg-app-prod --vnet-name vnet-app-prod --subnet snet-private \
  --private-connection-resource-id $(az keyvault show -n kv-app-prod --query id -o tsv) \
  --group-id vault --connection-name kv-conn

08Azure Policy and landing zones

Azure Policy evaluates every ARM request against rules: deny resources outside allowed regions, require tags, deny public IPs, enforce TLS 1.2, deploy diagnostic settings automatically. Effects include Deny, Audit, Modify and DeployIfNotExists, so policies can both block and fix. Assign them at management group level so every new subscription inherits them. Microsoft's Cloud Adoption Framework describes "landing zones": a pre-built structure of management groups, policies, central logging, networking and identity that new workloads plug into. For a small company that is overkill, but the core ideas scale down well: a platform subscription for shared things, separate application subscriptions, policies at the top, and infrastructure as code for everything. Defender for Cloud then scores your secure posture and flags misconfigurations.

09AKS in practice: tiers, upgrades and identity

AKS has a free control plane tier for experiments and a Standard tier with an uptime SLA, plus AKS Automatic, which preconfigures node autoscaling, security and monitoring in the spirit of GKE Autopilot. You still pay for the node VMs in standard clusters. Pick Azure CNI Overlay networking for most new clusters: pods get IPs from a private overlay range, so you do not exhaust VNet addresses. Use Microsoft Entra Workload ID so pods get tokens for their own managed identity through a Kubernetes service account, instead of mounting secrets. Upgrades matter: AKS supports only a window of Kubernetes versions, so set an auto-upgrade channel and planned maintenance windows, use multiple node pools (system and user), and test upgrades on staging. Container Insights and managed Prometheus give monitoring, but watch the Log Analytics ingestion cost.

Real-world scenarios

01

Zero-downtime releases for a .NET API on App Service

Problem

Every deploy to the production App Service causes a minute of errors while the app restarts and warms up.

How to solve it

Move to a Standard or Premium plan that supports deployment slots and create a staging slot. Deploy each release to staging, let it start, and configure applicationInitialization or a health check path so the swap waits until the app is warm. Mark environment-specific settings (connection strings, feature flags) as slot settings so they stay with the slot. Then run az webapp deployment slot swap --slot staging --target-slot production. Production traffic moves only after staging is healthy, and if something is wrong you swap back in seconds. Enable the App Service health check so unhealthy instances are removed automatically.

02

Removing passwords from an app's database connection

Problem

An API on Container Apps connects to Azure SQL with a username and password stored in app settings. An audit asks for passwordless access.

How to solve it

Enable a user-assigned managed identity on the container app. In Azure SQL, set an Entra admin, then create a contained user for the identity with CREATE USER [id-api-prod] FROM EXTERNAL PROVIDER and grant only db_datareader and db_datawriter or specific schema permissions. Change the connection string to use Authentication=Active Directory Managed Identity with the identity's client ID, or use the token from DefaultAzureCredential in your driver. Deploy, verify, then remove the password secret and disable SQL authentication on the server if no other client needs it. Monitor sign-in logs for failures during the switch.

03

A surprise bill from logs

Problem

A small app costs more in Log Analytics than in compute. Most ingestion comes from debug logs and container stdout.

How to solve it

Open the workspace's Usage table to find which tables and resources ingest most. Lower application log levels to warning in production and stop logging full request and response bodies. For Container Apps or AKS, filter noisy namespaces and use the Basic logs plan for high-volume tables you rarely query. Set a daily cap with an alert, shorten retention for verbose tables, and archive what compliance needs to cheaper storage. Sample traces in Application Insights instead of collecting everything. Add a budget alert on the resource group so the next increase is noticed in days, not at month end.

Cheat sheet

Log in and pick a subscription
az login && az account set --subscription "app-prod"
Create a resource group
az group create --name rg-app-dev --location westeurope
Delete a whole test environment
az group delete --name rg-app-dev --yes --no-wait
Deploy a Bicep file with preview
az deployment group what-if -g rg-app-prod -f main.bicep && az deployment group create -g rg-app-prod -f main.bicep
Container Apps: deploy from source
az containerapp up --name api --source . --resource-group rg-app-dev
Stream App Service logs
az webapp log tail --name app-api-prod --resource-group rg-app-prod
Swap a staging slot
az webapp deployment slot swap -g rg-app-prod -n app-api-prod --slot staging --target-slot production
Grant a role to a managed identity
az role assignment create --assignee <principal-id> --role "Key Vault Secrets User" --scope <vault-id>
Deallocate a VM (stops compute billing)
az vm deallocate -g rg-app-dev -n vm-test

Questions & answers

Q1What is the difference between App Service and Azure Container Apps?

App Service is a mature web hosting platform where you pay for an App Service plan, a fixed set of VMs that can host several apps. It offers deployment slots, built-in authentication, easy custom domains and supports code or containers. Container Apps is serverless containers on a managed Kubernetes base: you pay per vCPU and memory second, it scales on HTTP traffic, queue length or custom KEDA rules, can scale to zero and supports revisions, Dapr and background jobs. I pick App Service for classic web apps with steady traffic and teams that like slots. I pick Container Apps for microservices, event-driven workers and anything that should scale to zero.

Q2What is a managed identity and why use it?

A managed identity is an Entra ID identity that Azure creates and rotates for a resource such as an App Service, Container App, Function or VM. Your code asks the local platform endpoint for a token and uses it to call Key Vault, Storage, Azure SQL or any service that supports Entra authentication. There is no password or client secret to store, leak or rotate. System-assigned identities live and die with one resource; user-assigned identities are standalone and can be shared, which helps when several apps need the same access or when you want the identity to exist before the app. Combined with Azure RBAC, it gives least-privilege access without secrets.

Q3What is Entra ID and how does it relate to Azure?

Entra ID is Microsoft's cloud identity service, formerly called Azure Active Directory. It stores users, groups and application registrations for a tenant and provides sign-in, MFA, conditional access and single sign-on to Microsoft 365 and thousands of SaaS apps. Azure subscriptions trust one Entra tenant, so every person or app that manages Azure resources authenticates through it, and Azure RBAC grants them roles. For developers, Entra also acts as an OAuth 2.0 and OpenID Connect provider: you register an app, then let users sign in with their work accounts. Entra External ID covers customer-facing sign-in scenarios.

Q4How do you deploy to Azure from GitHub Actions without storing secrets?

Use OpenID Connect workload identity federation. Create an app registration or a user-assigned managed identity, add a federated credential that trusts tokens from GitHub for your specific repository and branch or environment, and grant it a narrow role, such as Contributor on one resource group or AcrPush on a registry. In the workflow, set permissions id-token: write and use azure/login with the client ID, tenant ID and subscription ID, which are identifiers, not secrets. GitHub issues a short-lived token per run and Azure exchanges it for an access token. Nothing long-lived is stored, and you can restrict production deploys to a protected environment.

Q5Bicep or Terraform for Azure?

Bicep is Microsoft's own language that compiles to ARM templates. It supports new Azure features on day one, needs no state file because Azure itself is the state, and has good VS Code tooling and what-if previews. Terraform with the azurerm provider is multi-cloud, has a large module ecosystem and a familiar plan and apply workflow, but you must manage remote state and occasionally wait for provider support of new features. If the company is Azure-only and uses Microsoft tooling, Bicep is a clean choice. If it runs several clouds or already uses Terraform, stick with Terraform. Either is far better than clicking in the portal.

Senior-level questions

S1How would you structure Azure for a company with 10 product teams?

Start from management groups: a platform group for shared services and a landing-zones group split into production and non-production. Each team or product gets its own subscriptions, created by an automated vending process that applies tags, budgets, diagnostic settings, policy assignments and networking (spoke VNet peered to a hub). Identity is centralised in Entra ID with groups mapped to RBAC roles at subscription or resource-group scope, and privileged access through Entra PIM with time-limited elevation. Logs flow to a central Log Analytics workspace, security posture through Defender for Cloud. Teams deploy with OIDC-federated pipelines and Bicep or Terraform modules from a shared catalogue. The trade-off is upfront platform work versus fewer incidents and audit pain later.

S2Cosmos DB: how do you choose a partition key and capacity mode?

The partition key decides both scalability and cost. A good key has high cardinality, spreads writes evenly and matches your most frequent queries, so most reads hit a single partition. For a multi-tenant SaaS, tenantId is natural, but large tenants can become hot; hierarchical partition keys (tenantId then userId) solve that. Cross-partition queries fan out and cost many request units. For capacity, serverless is cheapest for spiky, low-volume workloads; provisioned throughput with autoscale fits steady production traffic and caps at a maximum you choose; manual provisioned is cheapest only when load is very predictable. Also choose consistency carefully: session consistency is the default and right for most apps, strong costs more and limits multi-region writes.

S3App Service, Container Apps or AKS: how do you decide for a new product?

Ask what the team needs to control. App Service suits one or a few web apps with steady load, a team that values simplicity and slots, and predictable plan-based pricing. Container Apps suits multiple containerised services, event-driven workers and APIs with variable traffic, where scale to zero and KEDA rules save money and you do not want to run Kubernetes. AKS suits a platform team that needs the Kubernetes API: Helm charts, operators, custom networking, service mesh, GPU pools or portability across clouds, and that can own upgrades and cluster security. The cost of being wrong is asymmetric: moving from Container Apps to AKS later is straightforward with containers, while starting on AKS without the skills creates ongoing operational load.

Glossary

Tenant
An organisation's Entra ID directory, holding users, groups and app registrations; subscriptions trust one tenant.
Subscription
A billing and quota boundary that contains resource groups; roughly equivalent to an AWS account.
Resource group
A logical folder for resources that share a lifecycle; deleting it deletes everything inside.
Entra ID
Microsoft's cloud identity service, formerly Azure Active Directory, used for staff sign-in and app authentication.
Managed identity
An Azure-managed identity for a resource that gets tokens automatically, removing the need for stored secrets.
App Service plan
The set of VMs and pricing tier that App Service apps run on; billed whether apps are busy or idle.
Bicep
Microsoft's declarative language for Azure infrastructure that compiles to ARM templates and needs no state file.
Private endpoint
A private IP in your VNet that connects to a PaaS service so traffic avoids the public internet.
Flex Consumption
The recommended serverless plan for Azure Functions, with fast scaling, VNet support and optional always-ready instances.

Have a product in mind?Let's build it.

Tell me about your idea, your product or your team. I usually reply within one working day.