Terraform brings declarative control to Azure API Management, allowing the entire APIM lifecycle from service creation through products, policies, custom domains, and diagnostics to be expressed as code. The approach replaces manual portal changes with versioned, repeatable configuration that can be promoted across dev, test, and production environments.
Introduction
API Management in Azure is a control plane for exposing backend services with governance, security, and observability. When the control plane is built with Terraform, the infrastructure is described once and reproduced anywhere. The configuration covers the supporting resources required by APIM, the service itself with hostname bindings and security settings, and the operational components that make APIs production ready.
Core Terraform Concepts for APIM
A product in APIM is just another resource definition. If you need to create a number of products, then you can use a for-each construct to generate products based on a list, with just one resource declaration. Assigning a policy to a product is also straight forward.
The interesting thing here is that Terraform can remove products from APIM if you change the definition, unlike ARM templates. This drift removal behavior makes Terraform particularly effective for long term API catalog hygiene, where obsolete products can be pruned from code and automatically removed from the live service.
Provider Setup and Prerequisites
Provider versioning is explicit in APIM projects.
provider "azurerm" {
version = "=2.1.0"
features {}
}
The version of AzureRM used in my project is 2.1.0 which is the latest at the moment.
Current guidance for new projects uses a flexible constraint:
terraform {
required_version = ">= 1.3.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
Prerequisites for building and applying:
- Azure CLI configured with appropriate permissions
- Terraform installed version 1.0.0 or later
- Resource group created
- Understanding of API management concepts
- Azure subscription with Contributor access
- Azure CLI authenticated
- Azure subscription ID available as a Terraform variable, for example via TFVARsubscription_id
Some patience is required because APIM provisioning takes 30-45 minutes for Developer and Premium tiers.
Provider configuration with subscription variable:
variable "subscription_id" {
description = "Azure subscription ID used by the AzureRM provider."
type = string
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
Naming and Project Structure
To have consistent resource naming, we use a couple variables to construct names. Some resources in the definitions need to have an Azure-wide unique name, so the resource names are based on:
- prefix typically a company abbreviation
- resource function what is this resource about or for
- environment dev, test, acc, prd
- region region where deployed in
This leads for example to the following local variable:
locals {
resourceGroupName = "${var.prefix}-${var.resourceFunction}-${var.environment}-${var.region}"
}
The resource group name would become for example: didago-apim-dev-we
Consistent naming variables:
apimName = "${var.prefix}-${var.resourceFunction}-${var.environment}-${var.region}"
kvName = "${var.prefix}-${var.resourceFunction}-kv-${var.environment}-${var.region}"
appInsightsName = "${var.prefix}-${var.resourceFunction}-appinsights-${var.environment}-${var.region}"
Project structure recommended for APIM Terraform:
terraform-azure-apim/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│ └── apim/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── policies/
├── global.xml
├── product.xml
└── api.xml
Supporting Resources Before APIM
The idea is to use Terraform to setup an entire APIM configuration consisting of the following resources:
- Storage Account
- Key vault
- API Management + custom domain + configuration
- Application Insights
We need a Storage Account to store the Open API and APIM policy files in. To be able to import an API into APIM via Terraform or ARM, the Open API information must be publicly accessible. Open Api documents can sometimes be exposed on an endpoint by the API itself, but for policy files we need a place to host them.
We need Key vault to store the certificate necessary to setup custom domains. Key Vault will host the certificate which we need for our custom domain and in a later stage Key Vault can also contain API key secrets in case a backend API needs one.
API management is the core resource itself and we'll also add a default API to APIM to serve as heartbeat API for a load balancer.
Application insights will be the tool to store diagnostics information in.
All necessary files can be found in my github repo.
Terraform Resource Group and Storage Account Resources
First of all we need a resource group to store all resources in
Remark on state management:
However that will be created by the Terraform engine itself based on configuration setup in the definition when you perform the init step.
Remark: To simplify execution I've disabled the configuration of the backend, so the tfstate file is created on my local disk and not in a Storage Account. When running this in a DevOps release pipeline, then this definitely must be enabled and configured to use a Storage Account to keep track of the tfstate file.
API Management Service Configuration
Create modules/apim/main.tf
API Management Service
resource "azurerm_api_management" "main" {
name = "${var.project_name}-apim"
location = var.location
resource_group_name = var.resource_group_name
publisher_name = var.publisher_name
publisher_email = var.publisher_email
sku_name = "Premium_1"
zones = ["1", "2", "3"]
identity {
type = "SystemAssigned"
}
hostname_configuration {
proxy {
host_name = "api.${var.domain_name}"
key_vault_id = azurerm_key_vault_certificate.api.id
default_ssl_binding = true
negotiate_client_certificate = false
}
portal {
host_name = "portal.${var.domain_name}"
key_vault_id = azurerm_key_vault_certificate.portal.id
}
developer_portal {
host_name = "developer.${var.domain_name}"
key_vault_id = azurerm_key_vault_certificate.developer.id
}
}
security {
enable_backend_ssl30 = false
enable_backend_tls10 = false
enable_backend_tls11 =
A typical APIM instance definition:
```
resource "azurermresourcegroup" "apim" {
name = "rg-apim-prod"
location = "eastus"
tags = {
Environment = "Production"
}
}
Create the APIM instance
resource "azurermapimanagement" "main" {
name = "apim-prod-001"
location = azurermresourcegroup.apim.location
resourcegroupname = azurermresourcegroup.apim.name
publishername = "My Organization"
publisheremail = "[email protected]"
Tier options: Consumption, Developer, Basic, BasicV2, Standard, StandardV2, Premium, PremiumV2
Developer is good for non-production; Standard or Premium for production
skuname = "Developer1"
Managed identity for accessing backend services securely
identity {
type = "SystemAssigned"
}
Virtual network integration optional
virtualnetworktype = "Internal"
```
Tier selection guidance:
- Developer is good for non-production; Standard or Premium for production
- The Developer tier has the same features as Premium but without the SLA. Use it for development and testing, then move to Standard or Premium for production.
Configuration Table
| Component | Terraform Resource | Purpose |
|---|---|---|
| Resource Group | azurermresourcegroup | Holds all APIM related resources |
| Storage Account | azurermstorageaccount | Hosts OpenAPI specs and policy files publicly |
| Key Vault | azurermkeyvault | Stores certificates for custom domains and secrets |
| API Management | azurermapimanagement | Core APIM service with SKU and identity |
| Application Insights | azurermapplicationinsights | Stores diagnostics information |
Naming Convention Table
| Element | Example |
|---|---|
| prefix | didago |
| resource function | apim |
| environment | dev |
| region | we |
| resourceGroupName | didago-apim-dev-we |
| apimName | didago-apim-dev-we |
| kvName | didago-apim-kv-dev-we |
| appInsightsName | didago-apim-appinsights-dev-we |
Operational Best Practices
Use policies for cross-cutting concerns. Do not implement rate limiting, authentication, or CORS in your backend services when APIM can handle it. This keeps your backends focused on business logic.
Version your APIs. Use APIM's built-in versioning and revision features. Define API versions in Terraform so you can maintain backward compatibility.
Store sensitive values in Key Vault. Never hardcode API keys or secrets in Terraform files or APIM named values. Use Key Vault references.
Monitor everything. The Application Insights integration provides deep visibility into API usage, latency, and errors. Set up alerts for error rate spikes and latency increases.
With Terraform, you declare what you want and let the tool handle the rest.
Conclusion
Azure API Management with Terraform gives you a comprehensive, code-driven approach to API governance. From basic API proxying to advanced scenarios with JWT validation, rate limiting, and response caching, Terraform handles the full APIM configuration lifecycle. The initial setup takes some effort, but the payoff is a reproducible, auditable API platform that scales with your organization's needs.
The combination of declarative product generation via for-each, automatic removal of drifted products, consistent naming built from prefix, function, environment and region, and the supporting storage, key vault, and Application Insights resources creates a complete pipeline for building, promoting, and operating APIs as code. The Developer tier allows rapid iteration without SLA commitments, while Premium with zones and system assigned identity provides production readiness. Storing OpenAPI specs and policies in a public storage account satisfies import requirements, and key vault integration secures custom domain certificates and secrets throughout the lifecycle.