Skip to main content World Without Eng

Intro to Terraform providers

Published: 2022-12-30
Updated: 2023-10-31

Infrastructure as code is a paradigm that can apply to much more than just your cloud resources. With Terraform, you’re able to interact with any cloud or SaaS provider—or really any arbitrary API—as long as there’s a provider for it.

What is a provider?

A provider is a plugin that gives you a new set of resources and data for use in your Terraform code. As an example, check out the AWS provider documentation to see all the resources and data it provides.

How do I get a provider?

Providers are written in Go and published on the Terraform registry for clients to install and use. Clients can declare a new provider in their code using a provider block, which tells Terraform to install and use that provider. There may be configuration required in the provider block, such as passing in an API key. Here’s an example Datadog provider declaration with an API and app key passed in:

provider "datadog" {
  api_key = var.datadog_api_key
  app_key = var.datadog_app_key
}

“Requiring” providers

All declared providers must be configured in a required_providers block. This block is nested below the terraform block and it takes the provider’s local name (this is the name you gave the provider when you declared it, so in our example above it would be datadog), and the source and version as an argument. You can read more about this configuration block in the Terraform documentation. The format looks like this:

terraform {
  required_providers {
    local_name = {
      source = "foo/bar"
      version = "1.0.0"
    }
  }
}

The required_providers block can take more than one provider configuration if you happen to be using more than one provider in your Terraform. Here’s an example configuring the AWS, Aiven, and Datadog providers:

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "~> 4.6"
    }
    aiven = {
      source = "aiven/aiven"
      version = ">= 2"
    }
    datadog = {
      source = "DataDog/datadog"
    }
  }
}

Provider versions

Notice that the versions are all specified in different ways. You can pin a specific version, allow flexibility in the minor or patch versions, or leave the version unspecified and just use the latest. This syntax is explained in more detail in the Terraform docs.

Terraform’s provider versioning is syntactically similar to a Gemfile or a package.json file, and much like those package management systems, Terraform is capable of generating a lock file for your provider versions. If you run terraform init in the directory where your root Terraform file is, you’ll see a .terraform.hcl.lock file generated which can then be stored in version control.

Organizing your code

The required_providers configuration is typically done in a separate Terraform file called versions.tf or providers.tf. This file lives in the same directory as your main.tf file and other Terraform code. It’s not strictly necessary to put it in a separate file, but doing so can help keep your code neat. If you write a module, that module will have its own required_providers block that must live in the same directory as the rest of the code for that module.

Require rather than declare within a module

For those of you who are writing a module, do not declare the provider inside the module. Instead, require the provider in your required_providers block and allow your client to declare the provider. Provider blocks typically go in the root of a Terraform project. If you declare one inside of your module, you won’t be able to remove the module without deleting all the resources in it first, since removing the module outright will also remove the provider. Without the provider, there’s no way to de-provision the resources!

Using multiple of the same provider

Lastly, you may declare and use the same provider more than once. This can be useful if you want to use the same resources but with different configurations at the provider level. For example, in our Datadog provider above, we passed in an API key and an app key. If we were creating monitors for more than one app, we might want to declare two different Datadog providers with two different app keys.

Declaring two of the same provider is done by adding an alias to your provider declaration. For our Datadog provider, we might end up with something like this:

provider "datadog" {
  api_key = var.datadog_api_key
  app_key = var.datadog_app_key
}

provider "datadog" {
  alias   = "app2"
  api_key = var.datadog_api_key
  app_key = var.datadog_app_key_2
}

We will need to add a field called configuration_aliases to our required_providers for our new provider, assuming we want the configuration to be the same. That would look like this:

terraform {
  required_providers {
    datadog = {
      source  = "DataDog/datadog"
      version = "~> 3.19"
      configuration_aliases = [ datadog.app2 ]
    }
  }
}

Note that the provider without an alias is the default provider. If a provider isn’t specified for a resource, Terraform will use the default. To tell it to use the alternate provider, you must explicitly add the provider field to your resource. You pass in the provider with the format provider.alias, which looks like:

resource "datadog_monitor" "foo" {
  provider = datadog.app2
  ...
}

If you didn’t want the same configuration—let’s say you wanted to specify a different version—you’d have to use a different local name for your second provider, and you’d have to add a new entry to your required_providers for that local name. Continuing our example:

provider "datadog" {
  api_key = var.datadog_api_key
  app_key = var.datadog_app_key
}

provider "datadog_app2" {
  api_key = var.datadog_api_key
  app_key = var.datadog_app_key
}

terraform {
  required_providers {
    datadog = {
      source  = "DataDog/datadog"
      version = "~> 3.19"
    }
    datadog_app2 = {
      source  = "DataDog/datadog"
      version = "~> 2"
    }
  }
}

You will still need to pass the provider to your resources, albeit by the new local name rather than by alias if you want Terraform to use the alternate provider.

You can read more about multiple provider configurations in the Terraform docs.

Hopefully that helps you start branching out with your Terraform! You can start treating your Datadog monitors, GitHub repos, and PagerDuty teams as code, and the registry of providers keeps growing. Check out my article on provisioning Datadog resources for more information on that particular provider!

Check out the O'Reilly Terraform book if you want to learn more! This is the updated third edition of the book that I read when I first started learning Terraform. I'd highly recommend it! Also note, this is an affiliate link so I will get a small commission from Amazon if you buy the book through this link. Happy coding!