Using Terraform with Azure allows you to automate the provisioning and management of Azure resources using infrastructure as code (IaC).
Terraform supports almost all Azure services and integrates directly with the Azure Resource Manager (ARM) API. This means you can define infrastructure, such as virtual machines, networks, or databases, in code, track changes through version control, and collaborate easily across teams.
What we will cover in this article:
- What is the AzureRM Terraform provider?
- AzureRM Terraform provider configuration options
- Authentication methods for the Terraform Azure provider
- How to set up the AzureRM Terraform provider
- Versioning Terraform Azure provider
- Example: Creating Azure resources with the Azure provider
- Additional Azure Terraform providers
The AzureRM Terraform provider is a plugin that lets you manage Microsoft Azure infrastructure directly through Terraform. It acts as the bridge between your .tf configuration files and the Azure Resource Manager (ARM) API.
AzureRM Terraform provider allows you to provision services like Azure Kubernetes Service (AKS), App Services, Virtual Networks, Key Vaults, and more. This makes it a foundational tool for infrastructure as code (IaC) workflows within the Azure ecosystem.
Key features of the Terraform AzureRM provider
Key features of the AzureRM provider include:
- Comprehensive Azure resource coverage: Supports a wide range of Azure services including virtual machines, storage accounts, virtual networks, databases, and Kubernetes clusters.
- Support for modules and reusability: Encourages modular code through reusable components, making configurations more scalable and maintainable.
- Authentication options: Offers multiple authentication methods, including service principal, managed identity, and Azure CLI.
- Resource tagging and lifecycle rules: Supports tagging for organization and lifecycle blocks to manage resource creation and deletion behaviors.
Here are the most important configuration options for the AzureRM Terraform provider:
Option | Description |
features |
Required block to enable the provider (even if empty) |
subscription_id |
Explicitly set the Azure subscription to use |
client_id |
Azure AD app/client ID for authentication |
client_secret |
Secret/password for the client app |
tenant_id |
Azure AD tenant ID |
environment |
Azure cloud environment (e.g., public) |
client_certificate_path |
Path to a PEM/PKCS12 cert for auth (if not using secret) |
client_certificate_password |
Password for certificate authentication (required for .pfx certificates) |
skip_provider_registration |
Skip auto-registration of Azure resource providers |
auxiliary_tenant_ids |
List of tenant IDs for cross-tenant operations |
alias |
Useful for multi-provider configurations |
When using the Terraform Azure Provider (also known as the azurerm provider), there are multiple authentication methods available to securely connect Terraform to Azure. Each method has specific use cases and requirements. Here’s a detailed breakdown of each:
1. Service Principal with Client Secret (Recommended for automation)
This is the most commonly used authentication method in automation scenarios such as CI/CD pipelines or scheduled Terraform runs. A Service Principal acts as an identity for your Terraform deployment and is granted appropriate permissions via Azure Role-Based Access Control (RBAC).
You authenticate by providing a Client ID, Client Secret, Tenant ID, and Subscription ID. These can be configured via environment variables:
export ARM_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_CLIENT_SECRET="your-client-secret"
export ARM_SUBSCRIPTION_ID="your-subscription-id"
export ARM_TENANT_ID="your-tenant-id"
Or specified in the Terraform provider block:
provider "azurerm" {
features = {}
client_id = var.client_id
client_secret = var.client_secret
tenant_id = var.tenant_id
subscription_id = var.subscription_id
}
This method is straightforward to implement and widely supported, but it requires secure handling and rotation of secrets, so it’s recommended that they be managed using Azure Key Vault or other secret managers.
2. Service Principal with client certificate
This method replaces the secret with a certificate for authentication, offering enhanced security and centralized certificate lifecycle management.
provider "azurerm" {
features = {}
client_id = var.client_id
client_certificate_path = var.cert_path
client_certificate_password = var.cert_password
tenant_id = var.tenant_id
subscription_id = var.subscription_id
}
Note: Certificate management adds operational complexity, especially in CI/CD pipelines.
3. Azure CLI authentication
This method, ideal for local development, leverages an active Azure CLI session to authenticate Terraform. After logging in with az login, Terraform automatically uses the credentials from your session when no other explicit credentials are provided.
provider "azurerm" {
features = {}
}
This is not suitable for CI/CD or automation, as it requires an interactive login and local CLI.
4. Managed Identity (MSI) – System-assigned or User-assigned
This authentication method is supported, but note that the use_msi
flag is deprecated. Instead, Terraform will automatically use a managed identity when it detects it’s running in an Azure environment that provides one (e.g., VM, Azure DevOps, AKS).
Set the following environment variable:
export ARM_USE_MSI=true
For user-assigned identity:
export ARM_USE_MSI=true
export ARM_CLIENT_ID="client-id-of-user-assigned-msi"
5. Environment-based authentication
This method relies on environment variables to supply the necessary authentication credentials. The Azure provider supports a set of variables like ARM_CLIENT_ID
, ARM_CLIENT_SECRET
, ARM_TENANT_ID
, and ARM_SUBSCRIPTION_ID
.
These variables are often used in conjunction with CI/CD tools where injecting secrets at runtime is a common practice.
6. OIDC Federation (Workload Identity Federation)
This new authentication approach, supported from AzureRM v3.0+., allows Terraform to authenticate without using secrets or certificates. Instead, it uses an OpenID Connect (OIDC) token provided by an external identity provider like GitHub Actions, Azure DevOps, or another cloud-native platform.
The Azure AD application is configured to trust the token from that provider, which is then used to authenticate securely. Setting it up involves some one-time configuration in Azure and your identity provider, but once done, it offers a highly secure and scalable solution.
You need to set the following environment variables:
export ARM_CLIENT_ID="your-app-id"
export ARM_TENANT_ID="your-tenant-id"
export ARM_SUBSCRIPTION_ID="your-subscription-id"
export ARM_OIDC_TOKEN_FILE_PATH="/path/to/oidc-token"
Before you can start using Terraform to manage resources in Azure, you’ll need to set up the AzureRM provider. It’s a quick process, but getting it set up properly is important so everything runs smoothly.
Below are the steps you’ll need to follow.
Step 1: Ensure Terraform is installed
Make sure you have Terraform installed on your system. You can check by running:
terraform -v
Step 2: Create a working directory
This directory will contain your Terraform configuration files.
mkdir terraform-azure-setup
cd terraform-azure-setup
Step 3: Define the AzureRM provider in main.tf
Create a file named main.tf
where you define the AzureRM provider and resources.
Example content of main.tf
:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
required_version = ">= 1.0.0"
}
provider "azurerm" {
features {}
}
The features {}
block must be present, even if it’s empty. It’s used to configure provider-specific behaviors, for example, soft delete settings for storage accounts or log retention policies for certain services.
Step 4: Authenticate with Azure
Terraform needs credentials to provision Azure resources. We covered several authentication methods above, and in this example, we’ll use the Service Principal method.
Create the service principal:
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/<SUBSCRIPTION_ID>"
This command requires the user to be logged into the Azure CLI with the necessary permissions.
Then set these environment variables:
export ARM_CLIENT_ID="<APP_ID>"
export ARM_CLIENT_SECRET="<PASSWORD>"
export ARM_SUBSCRIPTION_ID="<SUBSCRIPTION_ID>"
export ARM_TENANT_ID="<TENANT_ID>"
Step 5: Initialize Terraform
Run the terraform init
command to download the AzureRM provider and initialize your configuration.
Step 6: Write a sample resource
All Azure resources must exist within a resource group. This resource group serves as a logical boundary for our infrastructure, enabling lifecycle control (e.g., tearing down the entire stack with a single terraform destroy
) and easier cost management.
Add a resource to test if the provider is working. For example:
resource "azurerm_resource_group" "example" {
name = "example-resources"
location = "East US"
}
Not all Azure services are available in every region. Check compatibility when selecting regions.
Step 7: Plan and apply
Run terraform plan
to see what resources will be created. Then, run terraform apply
to actually create them.
You can now verify your Azure portal to see the newly created resource group.
Terraform providers evolve constantly. Every update to the provider might introduce:
- New resources and features
- Bug fixes
- Deprecations or breaking changes
Without constraints, each terraform init
might pull in the latest version, which could break your code if it’s not compatible. That’s where version constraints come in.
You can define constraints in the terraform
block, usually in your root module:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.50"
}
}
required_version = ">= 1.1.0"
}
provider "azurerm" {
features {}
}
>~> 3.50
means: “Use any version from 3.50.0 up to, but not including, 4.0.0.”
Keep in mind that even minor updates can introduce changes in behavior. Pin versions intentionally and test changes.
Read more: How to manage different Terraform versions
In this practical example, we’ll provision a typical web application stack in Azure using Terraform and the AzureRM provider. This includes an App Service for hosting the application code and an Azure SQL Database for persistent storage.
This is a common deployment pattern for internal tools, business apps, or public-facing SaaS platforms. The goal is not just to provision resources, but to define a coherent, cloud-native infrastructure that’s repeatable, version-controlled, and easy to manage across environments.
We assume you are using Terraform v1.3+ and AzureRM provider v3.x or later.
1. Define the Azure resource group
We start by defining a resource_group
, which serves as a logical boundary for everything else we deploy.
resource "azurerm_resource_group" "rg" {
name = "example-webapp-rg"
location = "East US"
}
We’re choosing "East US"
as the location, but this could be any Azure region that suits your latency or compliance needs.
2. Create the app service plan
Before we can host a web application, we need to define its compute environment, the App Service Plan. This plan abstracts the underlying VM instances.
Here we use a Standard tier with size S1, which gives us support for custom domains and TLS. Autoscaling is available in Premium plans, so consider upgrading if needed for production workloads.
resource "azurerm_app_service_plan" "app_plan" {
name = "example-appservice-plan"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
sku {
tier = "Standard"
size = "S1"
}
}
This ensures all resources stay within the same logical and geographic boundaries.
3. Define the app service (web app)
Next, we define the actual web application. This resource will point to the App Service Plan we just created.
The app_settings
block allows us to configure runtime behavior. The setting "WEBSITE_RUN_FROM_PACKAGE" = "1"
tells Azure to run the app from a ZIP package, which is the recommended deployment strategy for predictable, versioned releases.
resource "azurerm_app_service" "app" {
name = "example-webapp"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
app_service_plan_id = azurerm_app_service_plan.app_plan.id
app_settings = {
"WEBSITE_RUN_FROM_PACKAGE" = "1"
}
connection_string {
name = "DefaultConnection"
type = "SQLAzure"
value = "Server=tcp:${azurerm_sql_server.sql_server.fqdn},1433;Initial Catalog=${azurerm_sql_database.sql_db.name};User ID=${azurerm_sql_server.sql_server.administrator_login};Password=StrongP@ssword123!;Encrypt=true;Connection Timeout=30;"
}
}
This setup is now ready to accept deployments via CI/CD tools like Azure DevOps, GitHub Actions, or other release automation pipelines.
4. Provision the Azure SQL server
For our backend database, we start by provisioning a logical SQL Server. This doesn’t host data directly, it acts as a container for one or more databases. You’ll usually deploy one SQL Server per application environment unless you have multi-tenancy or other architectural constraints.
We’re using static credentials here for simplicity. In production, secrets should be managed through Azure Key Vault and injected via Terraform using azurerm_key_vault_secret
, or through environment variables.
resource "azurerm_sql_server" "sql_server" {
name = "example-sqlserver123"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
version = "12.0"
administrator_login = "sqladminuser"
administrator_login_password = "StrongP@ssword123!"
}
5. Create the Azure SQL database
Now we define the actual database to which the app will connect. The sku_name = "S0"
offers a balance of performance and cost for most small to medium workloads.
resource "azurerm_sql_database" "sql_db" {
name = "exampledb"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
server_name = azurerm_sql_server.sql_server.name
sku_name = "S0"
}
For larger or variable workloads, consider vCore-based pricing tiers or autoscaling options.
6. Optional: Outputs
To make your infrastructure more accessible, especially in automated pipelines, define outputs:
output "app_url" {
value = azurerm_app_service.app.default_site_hostname
}
output "sql_server_fqdn" {
value = azurerm_sql_server.sql_server.fqdn
}
Besides AzureRM, Microsoft provides other Terraform providers for Azure-related services:
- AzAPI: Provides direct access to Azure Resource Manager APIs, which are useful for managing resources not yet supported by AzureRM.
- AzureAD: Manages Microsoft Entra (Azure Active Directory) resources like users, groups, and applications. Not all Entra features are available yet.
- AzureDevOps: Manages Azure DevOps resources such as pipelines and repositories.
- AzureStack: Manages Azure Stack Hub resources for hybrid cloud environments.
Terraform is really powerful, but to achieve an end-to-end secure Gitops approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:
- Policies (based on Open Policy Agent) – You can control how many approvals you need for runs, what kind of resources you can create, and what kind of parameters these resources can have, and you can also control the behavior when a pull request is open or merged.
- Multi-IaC workflows – Combine Terraform with Kubernetes, Ansible, and other infrastructure-as-code (IaC) tools such as OpenTofu, Pulumi, and CloudFormation, create dependencies among them, and share outputs
- Build self-service infrastructure – You can use Blueprints to build self-service infrastructure; simply complete a form to provision infrastructure based on Terraform and other supported tools.
- Integrations with any third-party tools – You can integrate with your favorite third-party tools and even build policies for them. For example, see how to integrate security tools in your workflows using Custom Inputs.
Spacelift enables you to create private workers inside your infrastructure, which helps you execute Spacelift-related workflows on your end. Read the documentation for more information on configuring private workers.
You can check it for free by creating a trial account or booking a demo with one of our engineers.
The AzureRM Terraform provider enables infrastructure management on Microsoft Azure using Terraform. It supports key features like resource creation, data sources, and tagging. In this article, we explored the AzureRM Terraform provider, from its features and setup to authentication, versioning, and resource creation.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on its existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Terraform management made easy
Spacelift effectively manages Terraform state, more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and includes many more features.