**Domain**: DevOps & Cloud Engineering
Domain: DevOps & Cloud Engineering Inheritance: inheritable Version: 1.0.0 Last Updated: 2026-02-01
Comprehensive patterns for defining, provisioning, and managing cloud infrastructure through declarative code. Covers major IaC tools (Terraform, Bicep, Pulumi, CloudFormation), best practices for modularity, state management, testing, and GitOps workflows.
Manual Infrastructure Infrastructure as Code
┌─────────────────────┐ ┌─────────────────────┐
│ Click in Console │ │ Write Code │
│ Document Steps │ │ Version Control │
│ Hope It's Repeated │ │ Review & Approve │
│ Drift Over Time │ │ Automated Deploy │
│ Unclear State │ │ Consistent State │
└─────────────────────┘ └─────────────────────┘
| Benefit | Description |
|---|---|
| Repeatability | Same code = same infrastructure, every time |
| Version Control | Track changes, rollback, audit history |
| Collaboration | Code review, PRs, shared ownership |
| Documentation | Code IS the documentation |
| Testing | Validate before deploy |
| Speed | Provision environments in minutes |
| Approach | Description | Tools |
|---|---|---|
| Declarative | Describe desired end state | Terraform, Bicep, CloudFormation |
| Imperative | Describe steps to reach state | Scripts, Ansible, Pulumi (optional) |
Prefer declarative — let the tool figure out how to reach the desired state.
| Tool | Provider | Language | State | Best For |
|---|---|---|---|---|
| Terraform | HashiCorp | HCL | Remote/Local | Multi-cloud, mature ecosystem |
| Bicep | Microsoft | Bicep DSL | Azure-managed | Azure-native, simple syntax |
| Pulumi | Pulumi | TS/Python/Go/C# | Managed/Self | Developers who prefer real languages |
| CloudFormation | AWS | YAML/JSON | AWS-managed | AWS-only, deep integration |
| ARM Templates | Microsoft | JSON | Azure-managed | Legacy Azure (prefer Bicep) |
| CDK | AWS | TS/Python/Java | AWS-managed | Developers on AWS |
infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── prod/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/
│ └── database/
└── shared/
└── providers.tf
# variables.tf
variable "environment" {
type = string
description = "Environment name (dev, staging, prod)"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "location" {
type = string
default = "eastus2"
description = "Azure region for resources"
}
# main.tf
resource "azurerm_resource_group" "main" {
name = "rg-${var.project}-${var.environment}"
location = var.location
tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project
}
}
# outputs.tf
output "resource_group_id" {
value = azurerm_resource_group.main.id
description = "The ID of the resource group"
}
# modules/app-service/variables.tf
variable "name" {
type = string
description = "Name of the App Service"
}
variable "resource_group_name" {
type = string
}
variable "location" {
type = string
}
variable "sku" {
type = object({
tier = string
size = string
})
default = {
tier = "Standard"
size = "S1"
}
}
# modules/app-service/main.tf
resource "azurerm_service_plan" "main" {
name = "asp-${var.name}"
resource_group_name = var.resource_group_name
location = var.location
os_type = "Linux"
sku_name = var.sku.size
}
resource "azurerm_linux_web_app" "main" {
name = "app-${var.name}"
resource_group_name = var.resource_group_name
location = var.location
service_plan_id = azurerm_service_plan.main.id
site_config {
always_on = var.sku.tier != "Free"
}
}
# Usage in environment
module "api" {
source = "../../modules/app-service"
name = "myapi-${var.environment}"
resource_group_name = azurerm_resource_group.main.name
location = var.location
sku = {
tier = var.environment == "prod" ? "Premium" : "Standard"
size = var.environment == "prod" ? "P1v3" : "S1"
}
}
# backend.tf
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstate"
container_name = "tfstate"
key = "myproject/dev/terraform.tfstate"
}
}
State Best Practices:
.tfstate files# Reference existing resources
data "azurerm_key_vault" "shared" {
name = "kv-shared-${var.environment}"
resource_group_name = "rg-shared-${var.environment}"
}
data "azurerm_key_vault_secret" "db_password" {
name = "db-admin-password"
key_vault_id = data.azurerm_key_vault.shared.id
}
# Use in resource
resource "azurerm_mssql_server" "main" {
name = "sql-${var.project}-${var.environment}"
administrator_login = "sqladmin"
administrator_login_password = data.azurerm_key_vault_secret.db_password.value
# ...
}
// main.bicep
targetScope = 'subscription'
@allowed(['dev', 'staging', 'prod'])
param environment string
param location string = 'eastus2'
var resourceGroupName = 'rg-myproject-${environment}'
resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
name: resourceGroupName
location: location
tags: {
Environment: environment
ManagedBy: 'Bicep'
}
}
module appService 'modules/app-service.bicep' = {
scope: rg
name: 'appServiceDeployment'
params: {
appName: 'app-myproject-${environment}'
location: location
sku: environment == 'prod' ? 'P1v3' : 'S1'
}
}
output resourceGroupId string = rg.id
output appServiceUrl string = appService.outputs.defaultHostName
// modules/app-service.bicep
@description('Name of the App Service')
param appName string
param location string = resourceGroup().location
@allowed(['F1', 'S1', 'P1v3'])
param sku string = 'S1'
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: 'asp-${appName}'
location: location
sku: {
name: sku
}
kind: 'linux'
properties: {
reserved: true
}
}
resource webApp 'Microsoft.Web/sites@2023-01-01' = {
name: appName
location: location
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'NODE|20-lts'
alwaysOn: sku != 'F1'
}
}
}
output defaultHostName string = webApp.properties.defaultHostName
output appServiceId string = webApp.id
| Aspect | Bicep | Terraform |
|---|---|---|
| Azure Support | Day-0 | Day-1 to Day-N |
| Multi-cloud | ❌ | ✅ |
| State Management | Azure-managed | Self-managed |
| Learning Curve | Lower | Moderate |
| Community Modules | Limited | Extensive |
| Type Safety | Strong | Moderate |
Recommendation: Bicep for Azure-only; Terraform for multi-cloud or complex scenarios.
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure-native";
const config = new pulumi.Config();
const environment = config.require("environment");
// Resource Group
const resourceGroup = new azure.resources.ResourceGroup("rg", {
resourceGroupName: `rg-myproject-${environment}`,
location: "eastus2",
tags: {
Environment: environment,
ManagedBy: "Pulumi",
},
});
// App Service Plan
const appServicePlan = new azure.web.AppServicePlan("asp", {
resourceGroupName: resourceGroup.name,
kind: "Linux",
reserved: true,
sku: {
name: environment === "prod" ? "P1v3" : "S1",
tier: environment === "prod" ? "Premium" : "Standard",
},
});
// Web App
const webApp = new azure.web.WebApp("app", {
resourceGroupName: resourceGroup.name,
serverFarmId: appServicePlan.id,
siteConfig: {
linuxFxVersion: "NODE|20-lts",
alwaysOn: true,
},
});
export const endpoint = pulumi.interpolate`https://${webApp.defaultHostName}`;
✅ Good fit:
❌ Consider alternatives:
Pattern: {resource-type}-{project}-{environment}-{region}-{instance}
Examples:
rg-myproject-prod-eus2 (Resource Group)
app-myproject-prod-eus2 (App Service)
sql-myproject-prod-eus2 (SQL Server)
kv-myproject-prod-eus2 (Key Vault)
st-myproject-prod-eus2 (Storage - no hyphens)
locals {
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "Terraform"
CostCenter = var.cost_center
Owner = var.owner_email
CreatedDate = timestamp()
}
}
resource "azurerm_resource_group" "main" {
name = "rg-${var.project}-${var.environment}"
location = var.location
tags = local.common_tags
}
❌ DON'T: Hard-code secrets in IaC
❌ DON'T: Store secrets in tfvars files
❌ DON'T: Commit secrets to version control
✅ DO: Use Key Vault / Secrets Manager
✅ DO: Reference secrets via data sources
✅ DO: Use CI/CD pipeline secrets
✅ DO: Use managed identities where possible
┌─────────────────────────────────────────────────────────┐
│ Same Code Base │
├─────────────────────────────────────────────────────────┤
│ │
│ dev.tfvars staging.tfvars prod.tfvars │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ SKU: S1 │ │ SKU: S1 │ │ SKU: P1v3│ │
│ │ Count:1 │ │ Count:2 │ │ Count:3 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ DEV │ │ STAGING │ │ PROD │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
# Terraform
terraform fmt -check
terraform validate
tflint
tfsec # Security scanning
checkov # Policy as code
# Bicep
az bicep build --file main.bicep # Syntax check
# tests/app_service_test.tftest.hcl
run "app_service_creates_correctly" {
command = plan
variables {
environment = "dev"
project = "test"
}
assert {
condition = azurerm_linux_web_app.main.site_config[0].always_on == true
error_message = "Always-on should be enabled"
}
}
# Deploy to ephemeral environment
terraform apply -auto-approve -var="environment=test-${BUILD_ID}"
# Run integration tests
npm test
# Destroy ephemeral environment
terraform destroy -auto-approve -var="environment=test-${BUILD_ID}"