The value of using multiple providers in Terraform becomes clear when you hit real production infrastructure, where you suddenly need to deploy resources to two or more AWS regions, multiple cloud providers, or apply changes across a dev and a prod account in the same run.
There are two ways of using multiple providers in Terraform. One is combining completely different providers, and the other is running several configurations of the same provider.
They solve different problems, and you will end up using both when working with infrastructure at scale.
In this article, we will cover:
- What does multiple providers actually mean in Terraform
- Explore the two main use cases you will encounter
- How to wire providers into modules correctly
- The limitations and constraints when using multiple Terraform providers
What does multiple providers mean in Terraform?
A provider is a plugin that lets Terraform interact with an API. The AWS provider talks to AWS, the Kubernetes provider talks to your cluster’s API server, and the random provider doesn’t talk to anything external at all; it just generates values locally.
You can use different Terraform providers in the same configuration. This can be useful for multi-cloud scenarios or when you simply want to leverage a particular service from a cloud provider, combining it with that provider’s offering. For example, leveraging the AWS provider to create an EKS cluster, then using the Kubernetes provider to deploy workloads into the same cluster, all in a single run, sharing the same state.
At the same time, you can use multiple instances of the same provider. When you need to declare the same provider more than once, each with different settings, use the alias meta-argument. In this case, you deploy to multiple regions or accounts within a single configuration.
Both cases live in the same place, in your root module, and they are very common on real infrastructure. Let’s explore each one of them with working examples.
Case 1: Using different providers together
In this case, you want to use two or more distinct providers in the same configuration because your infrastructure spans multiple platforms or services.
While this is optional, it’s good practice to declare every provider you need in the required_providers block within the terraform block. If you do this, Terraform will know which plugins to download and which version constraints to enforce.
After you add the providers in the required_providers block, you still need to declare them using a “provider” block. Each provider has its own arguments, so ensure you review the docs to understand how to configure them.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.30"
}
}
}
provider "aws" {
region = "eu-west-1"
}
data "aws_eks_cluster" "this" {
name = "eks"
}
data "aws_eks_cluster_auth" "this" {
name = "eks"
}
provider "kubernetes" {
host = data.aws_eks_cluster.this.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.this.token
}In this example, you are using two providers: one for AWS to manage the cloud infrastructure and another for Kubernetes to manage what runs in the cluster.
I used data sources from the AWS provider to point to an existing EKS cluster instead of creating one in the same config. You could’ve generated the provider from a resource as well, but the problem is that the provider might need its configuration values known at plan time, and those attributes aren’t known until the cluster is actually created.
This is why Terraform tends to break, and pointing a data source at an existing cluster avoids this, since the attributes are known up front. You could’ve, of course, done a terraform plan -target and include only the AWS resources for the first run as a workaround.
By specifying other resources, like in the example below, Terraform will know exactly to which provider to attribute them:
resource "aws_s3_bucket" "app_data" {
bucket = "my-app-data-bucket"
}
resource "kubernetes_namespace" "app" {
metadata {
name = "production"
}
}Case 2: Multiple configurations of the same provider
Using multiple instances of the same provider can help you manage resources in different cloud regions. By default, Terraform allows only one configuration per provider type. If you write two provider “aws” blocks with no distinction, Terraform throws an error, because it can’t figure out which configuration a given resource belongs to.
The solution for the above case is to use the alias meta-argument. An alias is a named, secondary configuration of the same provider. You keep one default provider block without an alias, then add as many aliased blocks as you need.
Here is an example of how a multi-region AWS setup looks:
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "west"
region = "us-west-2"
}
provider "aws" {
alias = "eu"
region = "eu-west-1"
}So, now you have three AWS providers. The default one in us-east-1, aws.west, and aws.eu. In case you want to use the non-default configuration on a resource, you should set the provider meta-argument explicitly in the resource configuration:
resource "aws_vpc" "primary" {
cidr_block = "10.0.0.0/16"
}
resource "aws_vpc" "west" {
provider = aws.west
cidr_block = "10.1.0.0/16"
}
resource "aws_vpc" "europe" {
provider = aws.eu
cidr_block = "10.2.0.0/16"
}The first VPC has no provider argument, so it is using the default configuration and will be deployed in us-east-1. For the last two, the alias meta-argument was used, and they will be deployed in their respective regions. All three are managed by a single configuration, a single state file, and a single apply.
The exact same approach also works for multiple AWS accounts. You configure each aliased provider with different credentials or a different assume_role block, and you can deploy across accounts in a single run.
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "prod"
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::3333333333333:role/terraform"
}
}As a best practice, you should always keep a default provider configuration, the one without an alias, even when you rely heavily on aliases. Resources and modules that don’t have a specified provider argument fall back to the default, and Terraform will complain if no provider is available.
Using multiple providers in Terraform modules
When talking about modules, the rule to remember is that child modules automatically inherit the default provider configuration, but they never inherit alias providers automatically. You need to explicitly pass an aliased provider.
For example, you have a module that needs to create resources in two regions. So it will set up VPC peering between the primary and secondary regions. Inside this module, you need to declare which provider configurations it expects using configuration_aliases in the required_providers block.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.0"
configuration_aliases = [aws.primary, aws.secondary]
}
}
}Within the module, resources refer to these configurations by their aliases, exactly as you would in the root module.
resource "aws_vpc" "primary" {
provider = aws.primary
cidr_block = "10.0.0.0/16"
}
resource "aws_vpc" "secondary" {
provider = aws.secondary
cidr_block = "10.1.0.0/16"
}When calling the module, you map your real provider configurations to the names the module expects using the providers argument inside the module block.
provider "aws" {
alias = "use1"
region = "us-east-1"
}
provider "aws" {
alias = "usw2"
region = "us-west-2"
}
module "network" {
source = "./modules/network"
providers = {
aws.primary = aws.use1
aws.secondary = aws.usw2
}
}In this way, the module stays portable, and you should never hardcode a region or account. The parent module becomes the single place where all environment wiring lives, making the module reusable across dev, staging, and prod.
Limitations and constraints when using multiple Terraform providers
There are several real constraints that you need to know about using multiple Terraform providers before you build something that cannot be applied:
- You cannot use variables or expressions in the
aliasargument: Provider blocks are resolved before Terraform evaluates most of the configuration. This means thealiasvalue must be a static string. You cannot dynamically generate provider configurations based on a variable or a list - You cannot use count or for_each to spread a resource across multiple providers: The provider argument can’t be set from
count.index,each.key, oreach.value, so you cannot span a single resource block across multiple provider instances. So, if you need the same resource in three regions, you either write three resource blocks or call a module three times, each with a different provider mapped in. - Aliased providers are never inherited by child modules: If a module needs an aliased provider, you must declare it with
configuration_aliasesand then pass it explicitly. - Provider aliases are not a replacement for workspaces: Aliases let you use multiple provider configurations in one run that share a state file, while workspaces give you multiple separate state files. In case you want a fully isolated state per environment, aliases alone will not give you that.
- Too many aliases are a sign that it’s time to restructure: If you find yourself juggling more than four or five aliases in a single configuration, that is usually a signal that your configuration is doing too much. You need to restructure by splitting into separate root modules, one per region or account, and use remote state data sources to reference outputs across them.
- Trying to remove a provider configuration while resources still depend on it will fail: If you delete an aliased provider block while resources still reference it, Terraform cannot plan because it has no configuration to communicate with that platform. You need to migrate or to destroy those resources first, then remove the provider.
Knowing these limits will help you save a lot of time before you start designing a configuration that looks clean on paper but cannot be applied in practice.
How to manage Terraform resources with Spacelift
Terraform is powerful, but a secure GitOps workflow needs more than a CLI. You need a platform that orchestrates your infrastructure workflows with the guardrails, visibility, and control to match.
Spacelift takes Terraform further with purpose-built infrastructure orchestration, including:
- Policy as code with Open Policy Agent (OPA): Control approvals, restrict the resources teams can create, validate configuration parameters, and define how runs behave when pull requests are opened or merged.
- Multi-IaC workflows: Orchestrate Terraform alongside Kubernetes, Ansible, OpenTofu, Pulumi, CloudFormation, and other tools. Model dependencies between workflows and share outputs across them.
- Governed self-service infrastructure: Use Templates to give developers a curated catalog they deploy from by filling out a form. Inputs are validated before anything runs, and every version is pinned to a VCS commit for repeatable results. Platform teams define what can be deployed and how, so developers self-serve without learning the underlying IaC. Blueprints remain available when you just need to create an independent, editable stack.
- AI-assisted provisioning with Spacelift Intelligence: An Infrastructure Assistant that understands your stacks, state, and runs, so you can ask questions, diagnose failed runs, and create policies in plain language. It pairs with Intent, an agentic deployment model that provisions non-critical infrastructure from natural language, no HCL required, while your policies, credentials, and audit trail still apply.
- Integrations with third-party tools: Connect Spacelift to the tools your teams already use, and extend governance across them. For example, you can integrate security tools into your workflows using Custom Inputs.
Keep IaC and GitOps as the system of record for production, and use Intent for fast, governed provisioning of tests, demos, and POCs.
Spacelift also lets you run private workers inside your own infrastructure, so you can execute workflows within your security perimeter. Read the documentation to learn more about configuring private workers.
Try Spacelift by creating a trial account or booking a demo. Spacelift supports teams that want developer self-service without giving up governance, auditability, or control.
Key points
Multiple providers in Terraform will help you solve two different problems. By using different providers together, you will orchestrate infrastructure across platforms like AWS and Kubernetes in a single run, with Terraform handling the dependencies between them. Using multiple configurations of the same provider through aliases lets you deploy across regions and accounts without duplicating your code.
Modules are usually the part that trips people up. While default providers are automatically passed down to child modules, aliased providers are not. You have to declare configuration_aliases inside the module and explicitly pass them through the providers map in the parent module.
You need to keep the constraints in mind as you build. Don’t use aliased providers with count or for_each on a resource to spread it across providers, no expressions in alias names, and a hard limit on how many aliases you should reasonably maintain before splitting things up. If you respect those boundaries, multiple providers give you a clean, single-configuration way to manage complex multi-region, multi-account, multi-platform infrastructure.
If you need help in managing your Terraform configurations across multiple providers by creating dependencies, taking advantage of governance, or implementing self-service infrastructure, learn more about Spacelift by booking a demo with one of our engineers.
Manage Terraform better with Spacelift
Orchestrate Terraform workflows with policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and more.
Frequently asked questions
How do I use multiple providers in Terraform?
Declare each provider in the root module’s required_providers block, then add a provider block to configure each one. Resources map to a provider by the type prefix (for example, aws_ maps to aws), or you can set the provider meta-argument explicitly.
What is a provider alias in Terraform?
A provider alias is a named secondary configuration of the same provider, defined with the alias argument, that lets you run multiple instances side by side (different regions, accounts, or credentials). Resources target it with a reference like provider = aws.west.
Can Terraform deploy to multiple cloud providers at once?
Yes. A single configuration can declare AWS, Azure, Google Cloud, and others together, so Terraform plans and applies changes across all of them in one run, with outputs from one provider passed as inputs to another.

