6 min read
Terraform Azure Provider Fundamentals
Introduction
Terraform has become the de facto standard for infrastructure as code across multiple cloud providers. The Azure Provider (azurerm) offers comprehensive coverage of Azure services, enabling you to manage your entire Azure infrastructure declaratively. This guide covers essential patterns and best practices.
Setting Up the Azure Provider
Basic Configuration
# versions.tf
terraform {
required_version = ">= 1.0.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 2.65"
}
}
backend "azurerm" {
resource_group_name = "tfstate-rg"
storage_account_name = "tfstatestorage"
container_name = "tfstate"
key = "terraform.tfstate"
}
}
# providers.tf
provider "azurerm" {
features {
key_vault {
purge_soft_delete_on_destroy = true
}
virtual_machine {
delete_os_disk_on_deletion = true
}
}
}
Authentication Methods
# Using Service Principal
provider "azurerm" {
features {}
subscription_id = var.subscription_id
client_id = var.client_id
client_secret = var.client_secret
tenant_id = var.tenant_id
}
# Using Azure CLI (for development)
provider "azurerm" {
features {}
# No credentials needed - uses az login
}
# Using Managed Identity
provider "azurerm" {
features {}
use_msi = true
}
Resource Group and Basic Resources
# variables.tf
variable "environment" {
type = string
description = "Environment name (dev, staging, prod)"
}
variable "location" {
type = string
default = "australiaeast"
description = "Azure region"
}
variable "project_name" {
type = string
description = "Project name for resource naming"
}
# main.tf
locals {
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
CreatedDate = timestamp()
}
name_prefix = "${var.project_name}-${var.environment}"
}
resource "azurerm_resource_group" "main" {
name = "rg-${local.name_prefix}"
location = var.location
tags = local.common_tags
}
App Service Infrastructure
# app-service.tf
resource "azurerm_app_service_plan" "main" {
name = "asp-${local.name_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
kind = "Linux"
reserved = true
sku {
tier = var.environment == "prod" ? "Standard" : "Basic"
size = var.environment == "prod" ? "S1" : "B1"
}
tags = local.common_tags
}
resource "azurerm_app_service" "main" {
name = "app-${local.name_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
app_service_plan_id = azurerm_app_service_plan.main.id
site_config {
linux_fx_version = "DOTNETCORE|6.0"
always_on = var.environment == "prod"
http2_enabled = true
min_tls_version = "1.2"
cors {
allowed_origins = var.cors_origins
support_credentials = true
}
}
app_settings = {
"ASPNETCORE_ENVIRONMENT" = var.environment == "prod" ? "Production" : "Development"
"APPLICATIONINSIGHTS_CONNECTION_STRING" = azurerm_application_insights.main.connection_string
"KeyVaultUri" = azurerm_key_vault.main.vault_uri
}
identity {
type = "SystemAssigned"
}
https_only = true
tags = local.common_tags
}
# Deployment slots for production
resource "azurerm_app_service_slot" "staging" {
count = var.environment == "prod" ? 1 : 0
name = "staging"
app_service_name = azurerm_app_service.main.name
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
app_service_plan_id = azurerm_app_service_plan.main.id
site_config {
linux_fx_version = "DOTNETCORE|6.0"
always_on = true
}
tags = local.common_tags
}
Azure SQL Database
# sql.tf
resource "azurerm_mssql_server" "main" {
name = "sql-${local.name_prefix}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "12.0"
administrator_login = var.sql_admin_username
administrator_login_password = var.sql_admin_password
minimum_tls_version = "1.2"
azuread_administrator {
login_username = var.aad_admin_username
object_id = var.aad_admin_object_id
}
tags = local.common_tags
}
resource "azurerm_mssql_database" "main" {
name = "sqldb-${local.name_prefix}"
server_id = azurerm_mssql_server.main.id
collation = "SQL_Latin1_General_CP1_CI_AS"
max_size_gb = var.environment == "prod" ? 50 : 2
sku_name = var.environment == "prod" ? "S1" : "Basic"
zone_redundant = var.environment == "prod"
short_term_retention_policy {
retention_days = var.environment == "prod" ? 35 : 7
}
long_term_retention_policy {
weekly_retention = var.environment == "prod" ? "P4W" : null
monthly_retention = var.environment == "prod" ? "P12M" : null
}
tags = local.common_tags
}
# Firewall rules
resource "azurerm_mssql_firewall_rule" "azure_services" {
name = "AllowAzureServices"
server_id = azurerm_mssql_server.main.id
start_ip_address = "0.0.0.0"
end_ip_address = "0.0.0.0"
}
# Virtual network rule for App Service
resource "azurerm_mssql_virtual_network_rule" "main" {
name = "app-service-vnet-rule"
server_id = azurerm_mssql_server.main.id
subnet_id = azurerm_subnet.app_service.id
}
Key Vault Integration
# keyvault.tf
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "main" {
name = "kv-${local.name_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
soft_delete_retention_days = 7
purge_protection_enabled = var.environment == "prod"
network_acls {
bypass = "AzureServices"
default_action = "Deny"
ip_rules = var.allowed_ip_ranges
virtual_network_subnet_ids = [azurerm_subnet.app_service.id]
}
tags = local.common_tags
}
# Access policy for Terraform service principal
resource "azurerm_key_vault_access_policy" "terraform" {
key_vault_id = azurerm_key_vault.main.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
secret_permissions = [
"Get", "List", "Set", "Delete", "Purge", "Recover"
]
key_permissions = [
"Get", "List", "Create", "Delete", "Update"
]
}
# Access policy for App Service
resource "azurerm_key_vault_access_policy" "app_service" {
key_vault_id = azurerm_key_vault.main.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azurerm_app_service.main.identity[0].principal_id
secret_permissions = ["Get", "List"]
}
# Store secrets
resource "azurerm_key_vault_secret" "sql_connection" {
name = "SqlConnectionString"
value = "Server=tcp:${azurerm_mssql_server.main.fully_qualified_domain_name},1433;Database=${azurerm_mssql_database.main.name};Authentication=Active Directory Managed Identity;"
key_vault_id = azurerm_key_vault.main.id
depends_on = [azurerm_key_vault_access_policy.terraform]
}
Virtual Network Configuration
# network.tf
resource "azurerm_virtual_network" "main" {
name = "vnet-${local.name_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
address_space = ["10.0.0.0/16"]
tags = local.common_tags
}
resource "azurerm_subnet" "app_service" {
name = "snet-app-service"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.1.0/24"]
delegation {
name = "app-service-delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = [
"Microsoft.Network/virtualNetworks/subnets/action"
]
}
}
service_endpoints = ["Microsoft.Sql", "Microsoft.KeyVault", "Microsoft.Storage"]
}
resource "azurerm_subnet" "private_endpoints" {
name = "snet-private-endpoints"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.2.0/24"]
enforce_private_link_endpoint_network_policies = true
}
# VNet integration for App Service
resource "azurerm_app_service_virtual_network_swift_connection" "main" {
app_service_id = azurerm_app_service.main.id
subnet_id = azurerm_subnet.app_service.id
}
Application Insights
# monitoring.tf
resource "azurerm_log_analytics_workspace" "main" {
name = "log-${local.name_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
sku = "PerGB2018"
retention_in_days = var.environment == "prod" ? 90 : 30
tags = local.common_tags
}
resource "azurerm_application_insights" "main" {
name = "appi-${local.name_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
workspace_id = azurerm_log_analytics_workspace.main.id
application_type = "web"
tags = local.common_tags
}
# Alerts
resource "azurerm_monitor_metric_alert" "high_cpu" {
name = "alert-high-cpu-${local.name_prefix}"
resource_group_name = azurerm_resource_group.main.name
scopes = [azurerm_app_service.main.id]
description = "Alert when CPU usage is high"
criteria {
metric_namespace = "Microsoft.Web/sites"
metric_name = "CpuPercentage"
aggregation = "Average"
operator = "GreaterThan"
threshold = 80
}
action {
action_group_id = azurerm_monitor_action_group.main.id
}
tags = local.common_tags
}
resource "azurerm_monitor_action_group" "main" {
name = "ag-${local.name_prefix}"
resource_group_name = azurerm_resource_group.main.name
short_name = "alerts"
email_receiver {
name = "ops-team"
email_address = var.alert_email
}
tags = local.common_tags
}
Outputs
# outputs.tf
output "resource_group_name" {
value = azurerm_resource_group.main.name
}
output "app_service_url" {
value = "https://${azurerm_app_service.main.default_site_hostname}"
}
output "app_service_identity_principal_id" {
value = azurerm_app_service.main.identity[0].principal_id
}
output "key_vault_uri" {
value = azurerm_key_vault.main.vault_uri
}
output "sql_server_fqdn" {
value = azurerm_mssql_server.main.fully_qualified_domain_name
}
output "application_insights_instrumentation_key" {
value = azurerm_application_insights.main.instrumentation_key
sensitive = true
}
Conclusion
Terraform with the Azure Provider offers a powerful way to manage Azure infrastructure declaratively. By organizing resources into logical files, using variables for environment-specific values, and leveraging data sources for existing resources, you create maintainable and repeatable infrastructure. Start with basic resources and progressively add networking, security, and monitoring configurations.