TrademarkTrademark
Features
Documentation

AzureRM Terraform Provider Overview

AzureRM Terraform provider guide: core resources, auth setup, best practices for deploying to Azure with Terraform.
Brendan ThompsonMay 26, 2025
AzureRM Terraform Provider Overview
Key takeaways
  • The AzureRM provider lets Terraform create, update, and destroy Microsoft Azure resources, and the author recommends it over AzAPI for most day-to-day configuration.
  • AzureRM requires the features block and a subscription_id, and supports authentication via Azure CLI, managed identity, service principal with certificate or secret, and OpenID Connect.
  • Using environment variables for provider configuration avoids hardcoding credentials and allows an orchestrator or CI/CD tool to manage them at runtime.
  • Version constraints in the required_providers block ensure an expected provider version and source, with the pessimistic constraint operator '~>' being the author's usual choice.

The provider is what makes Terraform and OpenTofu useful. It gives engineers a way to talk to a service over the internet so they can create, update, and destroy resources on it. The diagram below shows how that flow works. The service exposes an API, that API is wrapped in an SDK, and a Terraform provider is built on top using the Terraform Plugin Framework. HCL then consumes the provider as the configuration for your infrastructure as code, defining new resources and data sources. Before you can use a provider in your code, it has to be published to the Terraform registry or to a registry that implements the registry interface.

__wf_reserved_inherit

Terraform / OpenTofu Provider Flow

The rest of this article is about the Terraform AzureRM provider, which creates resources on Microsoft Azure from Terraform configuration files. Microsoft offers engineers two providers: the AzureRM provider already mentioned, and AzAPI. AzAPI gives you direct access to the Azure API and can be a good choice when a resource isn't yet available in AzureRM. For day-to-day configuration, though, I'd strongly suggest reaching for AzureRM, because its interface is clear and easy to work with. The AzureRM provider also gets very regular releases with a weekly cadence and fantastic technical support from HashiCorp, Microsoft, and the community. That cadence means security updates and bug fixes ship quickly, fewer snafus for us engineers, and it keeps you close to the latest features in both Azure and the provider framework.

The sections that follow walk through the provider configuration and then show some common Azure resources being deployed.

Configuration

Firstly, let's look at the most basic provider configuration object we can use for AzureRM:

Unfortunately, even with the recent release of Version 4 of the Azure provider, the features block is still required. This block can be used to customize the behaviour of specific Azure Provider resources. (Though I've never seen anyone use it.) The second requirement is the subscription_id argument, which scopes the provider to a specific Azure subscription. Instead of setting the argument directly you can use an environment variable, ARM_SUBSCRIPTION_ID. I generally reach for environment variables since they make it easier to change things at runtime. The piece still missing is authentication. We can't touch resources on the platform without authenticating to it first, and Azure gives us a few ways to do that:

So there's a fair range of ways to authenticate your AzureRM provider to Azure. Any of them would work in the examples that follow, but I'll demonstrate with the Service Principal with Client Secret, showing it both with environment variables and with arguments.

Provider Arguments

The below shows all the required arguments in order to have a properly authenticated AzureRM provider instance. Whilst for the sake of this example the client_secret has a value this should NEVER be done. Either utilize the environment variable or an input variable to pass that argument in at a bare minimum.

Environment Variables

First we set the environment variables. The example below covers both bash and PowerShell, since either one works.

And now the provider configuration itself, which is about as complex as it gets:

The benefit of environment variables is clear here. The provider needs no argument configuration of its own, and an external system such as an orchestrator or CI/CD tool can manage the values for you.

Provider Versions

Next up is constraining provider versions, which matters more than people expect. You do this in the terraform block of your configuration. The example below uses the pessimistic versioning constraint.

Keeping these blocks in required_providers makes sure you get the version you expect from the source you expect. It also helps if your organization mirrors providers internally for security and wants the provider pulled from your own instance rather than the public registry. These are the valid operators for version constraints:

  • =: Only the explicitly defined version can be used. (This behaviour is the same when no operator is provided)
  • !=: Excludes the exact defined version.
  • >, >=, <, <=: Version comparators against the defined version.
  • ~>: Ensures only the rightmost element of the defined version may increment (e.g. 1.0 allows for any 1.x but will not allow 2.x)

Nine times out of ten I will choose the last constraint known as the pessimistic version constraint.

Example

Now that the provider configuration makes sense, let's walk through one example that creates a few foundational Azure resources. It builds the following:

  • Resource Group — the container/group for resources within Azure a resource cannot exist outside of a Resource Group
  • Virtual Network — the networking construct used with Azure
  • Virtual Machine — an IaaS instance, this can be Windows or Linux and there are resource blocks for both of these individually.

First off we create a little helper in a locals block. This keeps naming consistent across every resource we provision:

Normally you'd drive this with input variables so it stays configurable, but since this is just an example I've opted for static values.

Next is the Resource Group, where all our resources will land. It also gives us a single consistent location for everything.

As you can see we are referencing our local.suffix, which will yield us the name rg-dev-aue-app.

The next thing for us to do is the network setup, this includes; Virtual Network, Subnet, and Network Interface. Other resources to consider here would be Network Security Groups and Application Security Groups.

The Virtual Network is the foundation of Azure networking, this will generally have a number of Subnets associated with it. Some of which are extremely specific such as the AzureFirewallSubnet which must be named exactly that for it to be consumable by the Azure Firewall.

Next let's look at the final resource within our example, the Virtual Machine, for this example I have selected to use a Linux Virtual Machine as it's the simplest to configure and nobody likes Windows anyway 😜.

The Virtual Machine takes on references for all resources that we have previously created. It will provision a Linux (Ubuntu specifically) instance within our given Resource Group and Virtual Network.

Wrapping Up

This post covered what a provider is and how the AzureRM provider works in practice: how to configure it and the authentication options open to us as engineers. We also walked through an example that provisions a handful of common resources with it. From here you have enough to stand up your own Azure infrastructure and start adding the resources your project needs.

About the author
Brendan Thompsonsolutions engineer at Scalr
Brendan Thompson is a solutions engineer at Scalr, specializing in Terraform and cloud infrastructure.