Last reviewed: May 2026
Build the AWS services on the AZ-400 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 AZ-400 infrastructure side of a CI/CD pipeline — an Azure Container Registry to publish images into, an AKS cluster as the deployment target with managed identity and Log Analytics integration, a Key Vault for pipeline secrets, and the role assignments wiring everything to AKS's kubelet identity so it can pull images and read secrets without credentials in pipeline YAML.
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).AKS is the headline cost here:
~$75/month total while running. The most-tested AZ-400 cost-anti-pattern is leaving an unused AKS cluster running — destroy or stop the cluster when not in use.
Standard Azure opener.
terraform {
required_version = ">= 1.5"
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
random = { source = "hashicorp/random", version = "~> 3.6" }
}
}
provider "azurerm" {
features {
key_vault {
purge_soft_delete_on_destroy = true
}
}
}
resource "random_id" "suffix" {
byte_length = 3
}
data "azurerm_client_config" "current" {}
locals {
tags = {
Project = "certlabpro-az-400"
ManagedBy = "terraform"
}
}
resource "azurerm_resource_group" "main" {
name = "certlabpro-az-400-rg"
location = "eastus"
tags = local.tags
}ACR is where the CI pipeline pushes container images that the CD pipeline pulls. AZ-400 tests this push/pull boundary as a security pattern: the build pipeline gets AcrPush, the deployment target gets AcrPull, and never the twain shall meet.
We use the Basic tier — cheapest, single replication target. Premium adds geo-replication and the Content Trust feature (image signing); both are AZ-400 exam topics but cost-prohibitive for the lab.
resource "azurerm_container_registry" "main" {
name = "acrcertlabpro${random_id.suffix.hex}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
sku = "Basic"
admin_enabled = false # admin user disabled — Entra-only access
tags = local.tags
}AKS is the AZ-400 deployment target of choice for container workloads. We use a system-assigned managed identity (the modern default; service-principal auth is deprecating), a single small system nodepool, and azure_policy_enabled = true (the AZ-400 Implement compliance answer for cluster-level policy enforcement via Azure Policy add-on).
The Log Analytics workspace + OMS agent integration is the AZ-400 Implement instrumentation answer for cluster-level observability — every pod log, every node metric, every Kubernetes audit event lands in Log Analytics for KQL querying.
resource "azurerm_log_analytics_workspace" "main" {
name = "log-az400"
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_kubernetes_cluster" "main" {
name = "aks-az400"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
dns_prefix = "azaks${random_id.suffix.hex}"
default_node_pool {
name = "system"
node_count = 1
vm_size = "Standard_D2s_v3"
}
identity {
type = "SystemAssigned"
}
oms_agent {
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
}
azure_policy_enabled = true
tags = local.tags
}
# Grant the AKS kubelet identity AcrPull on the registry from Step 2.
# This is the AZ-400 password-less image-pull pattern.
resource "azurerm_role_assignment" "aks_acr_pull" {
scope = azurerm_container_registry.main.id
role_definition_name = "AcrPull"
principal_id = azurerm_kubernetes_cluster.main.kubelet_identity[0].object_id
}Pipeline secrets — API keys, deployment credentials, third-party tokens — should never sit in pipeline YAML. AZ-400's Implement security and validate code bases for compliance domain tests this Key-Vault-Reference-in-pipeline-task pattern: pipelines reference secrets by name from Key Vault at runtime.
We grant the AKS kubelet identity Key Vault Secrets User so deployed pods can also read secrets via the Secrets Store CSI Driver (the in-cluster mount mechanism for Key Vault-backed secrets). With the role in place, every pod that needs a database password reads it via the CSI driver, not from an env var baked into the image.
resource "azurerm_key_vault" "main" {
name = "kv-az400-${random_id.suffix.hex}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
enable_rbac_authorization = true
soft_delete_retention_days = 7
tags = local.tags
}
resource "azurerm_role_assignment" "kv_admin_self" {
scope = azurerm_key_vault.main.id
role_definition_name = "Key Vault Administrator"
principal_id = data.azurerm_client_config.current.object_id
}
resource "azurerm_role_assignment" "aks_kv_reader" {
scope = azurerm_key_vault.main.id
role_definition_name = "Key Vault Secrets User"
principal_id = azurerm_kubernetes_cluster.main.kubelet_identity[0].object_id
}Log Analytics handles cluster-level signals (node metrics, pod logs); App Insights handles application-level (request tracing, exception telemetry, dependency tracking, distributed traces). AZ-400 Implement instrumentation tests both layers — the exam expects you to wire them as a pair, with App Insights as the workspace-based mode using the Log Analytics workspace from Step 3.
With this final piece in place, the AZ-400 CI/CD-target shape is complete: ACR receives build images, AKS deploys them, Key Vault provides secrets, Log Analytics + App Insights observe the result. The pipeline YAML (Azure DevOps or GitHub Actions) drives it all — that lives in the source repo, not in Terraform.
resource "azurerm_application_insights" "main" {
name = "appi-az400"
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
}terraform destroy tears down everything. AKS is the line item billing $70/month for the system node — destroy promptly. ACR Basic is $5/month. The Log Analytics workspace retains data for the retention period (30 days here) even after destroy of consumers; the workspace itself destroys cleanly.
AZ-400 covers Azure DevOps Services and GitHub-on-Azure surfaces that don't live in the azurerm provider — Azure DevOps organizations, projects, repos, pipelines, agent pools, environments, variable groups, service connections (all in the separate azuredevops provider), GitHub Advanced Security for Azure DevOps, and the Application Insights Live Metrics + Smart Detection rules.
We stick to the deployment-target infrastructure because it's the substrate AZ-400 pipelines actually deploy onto. Once ACR + AKS + Key Vault + observability are in place, the pipeline YAML (in your source repo, not in Terraform) does the build-push-deploy work via the standard Azure CLI tasks.
For coverage of Azure DevOps Services + GitHub Actions patterns, see the Browse, Playbook, and Editorial sections of this cert page.