Last reviewed: May 2026
Build the AWS services on the AZ-204 exam with plain Terraform — one block at a time, each tied back to an exam domain. The same code works on OpenTofu.
By the end of this lab you'll have provisioned, with plain Terraform, the canonical Azure PaaS web app — an App Service Plan + Linux Web App with managed identity, a Cosmos DB account for state, an Application Insights workspace for telemetry, and a Storage Account the app can use for blob/file work. Five blocks; the AZ-204 reference architecture.
Drop the snippets into a single main.tf, run terraform init, then terraform apply step-by-step.
>= 1.5 or OpenTofu >= 1.6.az login).az webapp deploy). This lab provisions the infrastructure shell — code deployment is the AZ-204 Implement DevOps domain and lives outside Terraform.Whole stack idles at ~$13/month with the B1 plan. Switch to F1 for free testing (remove the managed identity in Step 3) or destroy promptly.
Standard Azure opener. App Service web app names must be globally unique (DNS-published as <name>.azurewebsites.net) — random_id dodges collisions.
terraform {
required_version = ">= 1.5"
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
random = { source = "hashicorp/random", version = "~> 3.6" }
}
}
provider "azurerm" {
features {}
}
resource "random_id" "suffix" {
byte_length = 3
}
locals {
tags = {
Project = "certlabpro-az-204"
ManagedBy = "terraform"
}
}
resource "azurerm_resource_group" "main" {
name = "certlabpro-az-204-rg"
location = "eastus"
tags = local.tags
}AZ-204 expects you to reach for Cosmos DB whenever the question says single-digit-millisecond at scale, globally distributed, or NoSQL with SQL-like querying. Serverless capacity mode is the cost-optimization default — pay-per-RU, $0 idle, exam-tested as the right choice for low-volume / dev environments.
We create the account, one database under it, and one container with a partition key. The partition key choice (/userId here) is the most-tested AZ-204 Cosmos question — bad partition keys cause hot partitions; good ones evenly distribute load. The exam tests this pattern with cardinality scenarios.
resource "azurerm_cosmosdb_account" "main" {
name = "certlabpro-az-204-${random_id.suffix.hex}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
offer_type = "Standard"
kind = "GlobalDocumentDB"
capabilities {
name = "EnableServerless"
}
consistency_policy {
consistency_level = "Session"
}
geo_location {
location = azurerm_resource_group.main.location
failover_priority = 0
}
tags = local.tags
}
resource "azurerm_cosmosdb_sql_database" "app" {
name = "appdb"
resource_group_name = azurerm_resource_group.main.name
account_name = azurerm_cosmosdb_account.main.name
}
resource "azurerm_cosmosdb_sql_container" "users" {
name = "users"
resource_group_name = azurerm_resource_group.main.name
account_name = azurerm_cosmosdb_account.main.name
database_name = azurerm_cosmosdb_sql_database.app.name
partition_key_paths = ["/userId"]
}AZ-204's Implement Azure Security and Monitor, Troubleshoot, and Optimize domains both lean on Application Insights — the APM service for distributed traces, dependency tracking, exception capture, and live metrics. Modern App Insights instances require a Log Analytics workspace as their data backend (the workspace-based mode; classic mode is deprecated).
We create both and the instrumentation key + connection string come out as App Insights attributes — wired into the App Service in Step 4 via app settings.
resource "azurerm_log_analytics_workspace" "main" {
name = "certlabpro-az-204-logs"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
sku = "PerGB2018"
retention_in_days = 30
tags = local.tags
}
resource "azurerm_application_insights" "main" {
name = "certlabpro-az-204-appi"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
workspace_id = azurerm_log_analytics_workspace.main.id
application_type = "web"
tags = local.tags
}App Service is the AZ-204 PaaS web-host of choice. The App Service Plan is the compute container (think EC2 underneath the abstraction); web apps inside it share the plan's compute. We use a Linux B1 plan and a single Web App with the Node.js 20 runtime — swap to python, dotnet, java, or php to match your stack.
The system-assigned managed identity is the AZ-204 password-less-credential answer for the app accessing other Azure services (Key Vault, Storage, Cosmos DB). Once granted RBAC roles on those resources, the App Service calls them with Entra-issued tokens — no connection strings in app settings.
The app_settings block injects environment variables: App Insights connection string, Cosmos endpoint, and a flag. The web app starts as an empty shell — code deploys separately. The lab is about getting the infrastructure right.
resource "azurerm_service_plan" "main" {
name = "certlabpro-az-204-plan"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
os_type = "Linux"
sku_name = "B1"
tags = local.tags
}
resource "azurerm_linux_web_app" "main" {
name = "certlabpro-az-204-${random_id.suffix.hex}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_service_plan.main.location
service_plan_id = azurerm_service_plan.main.id
https_only = true
site_config {
always_on = true
application_stack {
node_version = "20-lts"
}
}
identity {
type = "SystemAssigned"
}
app_settings = {
APPLICATIONINSIGHTS_CONNECTION_STRING = azurerm_application_insights.main.connection_string
COSMOS_ENDPOINT = azurerm_cosmosdb_account.main.endpoint
WEBSITES_ENABLE_APP_SERVICE_STORAGE = "false"
}
tags = local.tags
}
# Grant the web app's managed identity Cosmos DB data-plane access
# (Cosmos DB Built-in Data Contributor — the AZ-204 password-less pattern).
resource "azurerm_cosmosdb_sql_role_assignment" "webapp" {
resource_group_name = azurerm_resource_group.main.name
account_name = azurerm_cosmosdb_account.main.name
role_definition_id = "${azurerm_cosmosdb_account.main.id}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002"
principal_id = azurerm_linux_web_app.main.identity[0].principal_id
scope = azurerm_cosmosdb_account.main.id
}Every Azure PaaS web app eventually needs blob/file storage — uploaded files, user avatars, generated PDFs, processed images. We provision the account with standard secure defaults and grant the app's managed identity Storage Blob Data Contributor so the app can read/write blobs without a connection string.
This closes the AZ-204 Develop Azure Compute Solutions loop: app + state (Cosmos) + telemetry (App Insights) + blob storage, all wired via managed identity instead of secrets. Every additional AZ-204 pattern (Service Bus queue, Event Grid topic, Key Vault secrets, Logic App integration) attaches the same way — give the managed identity the right role at the right scope.
resource "azurerm_storage_account" "app_blobs" {
name = "az204app${random_id.suffix.hex}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "LRS"
account_kind = "StorageV2"
https_traffic_only_enabled = true
min_tls_version = "TLS1_2"
allow_nested_items_to_be_public = false
tags = local.tags
}
resource "azurerm_role_assignment" "webapp_blobs" {
scope = azurerm_storage_account.app_blobs.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = azurerm_linux_web_app.main.identity[0].principal_id
}terraform destroy tears down everything. The App Service Plan B1 is the line item billing 24/7 (~$13/month). The plan stays in the RG even after the web app is removed if you scale-in incorrectly — terraform destroy removes both. Cosmos DB serverless and App Insights stop billing immediately on destroy.
AZ-204 covers more developer services this lab can't fit — Azure Functions (event-driven serverless), Container Apps, AKS, Service Bus, Event Grid, Event Hubs, Logic Apps, Durable Functions, API Management, Front Door, CDN, Notification Hubs, SignalR, and Azure Key Vault for secrets storage (a common AZ-204 follow-up after managed identity).
We stick to the App Service + Cosmos + App Insights + Storage baseline because it's the most-tested PaaS web-app shape on the exam — and every other AZ-204 pattern (Functions, Service Bus, Key Vault) attaches to this base via managed identity + RBAC.
For the surfaces above, see the Browse, Playbook, and Editorial sections of this cert page.