Skip to main content

Enhanced Region Support in AWS

15 min

Version 7.0.0 of the Pulumi AWS Provider adds a top-level region to most resources, making it significantly easier to manage infrastructure across AWS Regions without requiring multiple provider configurations.

Before v7.0.0, managing infrastructure across multiple Regions required a separate provider configuration for each Region, which led to complex and repetitive configurations—especially for large infrastructures.

In this tutorial, you will learn how the new per-resource region argument works, how to migrate from multiple provider configurations to a single provider, and see before-and-after examples for common multi-Region patterns.

What you'll learn
  • What the per-resource region argument does in AWS Provider v7
  • How region works, including validation, replacement behavior, and imports
  • How to migrate from multiple provider configurations to a single provider
  • How to apply region to real-world patterns like VPC peering, KMS replica keys, and S3 replication
  • Which resources are not Region-aware
Prerequisites

Version 7.0.0 of the Pulumi AWS Provider adds region to most resources, making it significantly easier to manage infrastructure across AWS Regions without requiring multiple provider configurations.

What’s new#

As of v7.0.0, most existing resources and data sources are now Region-aware, meaning they support a new top-level region. This allows you to manage a resource in a Region different from the one specified in the provider configuration without requiring multiple provider definitions. See How region works for details.

For example, if your provider is configured for us-east-1, you can now manage a VPC in us-west-2 without defining an additional provider:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const peer = new aws.ec2.Vpc("peer", {
region: "us-west-2",
cidrBlock: "10.1.0.0/16",
});

What’s not changing#

Pre-v7.0.0 configurations that use provider configurations per Region remain valid in v7.0.0 and are not deprecated.

You can still define the Region at the provider level using any of the existing methods—for example, through the AWS config file, provider configuration, environment variables, shared configuration files, or explicitly using the provider’s region.

Can I use region in every resource?#

No. While most resources are now Region-aware, there are exceptions. These include a few resources that already had a region and resources that are inherently global. See Non–region-aware resources.

Why make this change#

Before version 7.0.0, managing infrastructure across multiple Regions required a separate provider configuration for each Region. This approach led to complex and repetitive configurations, especially for large infrastructures—AWS currently operates in 36 Regions, with more announced. Additionally, each provider configuration adds overhead in terms of memory and compute resources.

See the examples below for a comparison of configurations before and after introducing region.

How region works#

The new top-level region is Optional, and defaults to the Region specified in the provider configuration. Its value is validated to ensure it belongs to the configured partition. Changing the value of region will force resource replacement.

To import a resource in a specific Region, append @<region> to the import ID—for example:

Terminal window
pulumi import aws:ec2/vpc:Vpc test_vpc vpc-a01106c2@eu-west-1

Migrating from multiple provider configurations#

To migrate from a separate provider configuration for each Region to a single provider configuration and per-resource region values you must ensure that Pulumi state is refreshed:

  1. Upgrade to v7.0.0 following the upgrade guide.
  2. Modify the affected resource configurations, replacing the provider resource option with a region argument
  3. Run Pulumi with refresh – pulumi up --refresh

Before and after examples using region#

Cross-region VPC peering#

Before, Pre-v7.0.0

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const awsPeerProvider = new aws.Provider("awsPeerProvider", {region: "us-west-2"});
const peerIdentity = aws.getCallerIdentityOutput({}, {
provider: awsPeerProvider,
});
const awsProvider = new aws.Provider("awsProvider", {region: "us-east-1"});
const main = new aws.ec2.Vpc("main", {cidrBlock: "10.0.0.0/16"}, {
provider: awsProvider,
});
const peer = new aws.ec2.Vpc("peer", {cidrBlock: "10.1.0.0/16"}, {
provider: awsPeerProvider,
});
const peerConnection = new aws.ec2.VpcPeeringConnection("peerConnection", {
vpcId: main.id,
peerVpcId: peer.id,
peerOwnerId: peerIdentity.apply(peerIdentity => peerIdentity.accountId),
peerRegion: "us-west-2",
autoAccept: false,
}, {
provider: awsProvider,
});
const peerAccepter = new aws.ec2.VpcPeeringConnectionAccepter("peerAccepter", {
vpcPeeringConnectionId: peerConnection.id,
autoAccept: true,
}, {
provider: awsPeerProvider,
});

After, v7.0.0+

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const awsProvider = new aws.Provider("awsProvider", {region: "us-east-1"});
const main = new aws.ec2.Vpc("main", {cidrBlock: "10.0.0.0/16"}, {
provider: awsProvider,
});
const peer = new aws.ec2.Vpc("peer", {
region: "us-west-2",
cidrBlock: "10.1.0.0/16",
}, {
provider: awsProvider,
});
const peerConnection = new aws.ec2.VpcPeeringConnection("peerConnection", {
vpcId: main.id,
peerVpcId: peer.id,
peerRegion: "us-west-2",
autoAccept: false,
}, {
provider: awsProvider,
});
const peerAccepter = new aws.ec2.VpcPeeringConnectionAccepter("peerAccepter", {
region: "us-west-2",
vpcPeeringConnectionId: peerConnection.id,
autoAccept: true,
}, {
provider: awsProvider,
});

KMS replica key#

Before, Pre-v7.0.0

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const awsPrimaryProvider = new aws.Provider("awsPrimaryProvider", {region: "us-east-1"});
const awsProvider = new aws.Provider("awsProvider", {region: "us-west-2"});
const primary = new aws.kms.Key("primary", {
description: "Multi-Region primary key",
deletionWindowInDays: 30,
multiRegion: true,
}, {
provider: awsPrimaryProvider,
});
const replica = new aws.kms.ReplicaKey("replica", {
description: "Multi-Region replica key",
deletionWindowInDays: 7,
primaryKeyArn: primary.arn,
}, {
provider: awsProvider,
});

After, v7.0.0

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const awsProvider = new aws.Provider("awsProvider", {region: "us-west-2"});
const primary = new aws.kms.Key("primary", {
region: "us-east-1",
description: "Multi-Region primary key",
deletionWindowInDays: 30,
multiRegion: true,
}, {
provider: awsProvider,
});
const replica = new aws.kms.ReplicaKey("replica", {
description: "Multi-Region replica key",
deletionWindowInDays: 7,
primaryKeyArn: primary.arn,
}, {
provider: awsProvider,
});

S3 bucket replication configuration#

Before, Pre-v7.0.0

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const assumeRoleDocument = aws.iam.getPolicyDocumentOutput({
statements: [{
effect: "Allow",
principals: [{
type: "Service",
identifiers: ["s3.amazonaws.com"],
}],
actions: ["sts:AssumeRole"],
}],
});
const awsProvider = new aws.Provider("awsProvider", {region: "eu-west-1"});
const destination = new aws.s3.Bucket("destination", {}, {
provider: awsProvider,
});
const awsCentralProvider = new aws.Provider("awsCentralProvider", {region: "eu-central-1"});
const source = new aws.s3.Bucket("source", {}, {
provider: awsCentralProvider,
});
const replicationPolicyDocument = aws.iam.getPolicyDocumentOutput({
statements: [
{
effect: "Allow",
actions: [
"s3:GetReplicationConfiguration",
"s3:ListBucket",
],
resources: [source.arn],
},
{
effect: "Allow",
actions: [
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionTagging",
],
resources: [pulumi.interpolate`${source.arn}/*`],
},
{
effect: "Allow",
actions: [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags",
],
resources: [pulumi.interpolate`${destination.arn}/*`],
},
],
});
const replicationRole = new aws.iam.Role("replicationRole", {assumeRolePolicy: assumeRoleDocument.apply(assumeRoleDocument => assumeRoleDocument.json)}, {
provider: awsProvider,
});
const replicationPolicy = new aws.iam.Policy("replicationPolicy", {policy: replicationPolicyDocument.apply(replicationPolicyDocument => replicationPolicyDocument.json)}, {
provider: awsProvider,
});
const replicationPolicyAttachment = new aws.iam.RolePolicyAttachment("replicationPolicyAttachment", {
role: replicationRole.name,
policyArn: replicationPolicy.arn,
}, {
provider: awsProvider,
});
const destinationVersioning = new aws.s3.BucketVersioning("destinationVersioning", {
bucket: destination.id,
versioningConfiguration: {
status: "Enabled",
},
}, {
provider: awsProvider,
});
const sourceBucketAcl = new aws.s3.BucketAcl("sourceBucketAcl", {
bucket: source.id,
acl: "private",
}, {
provider: awsCentralProvider,
});
const sourceVersioning = new aws.s3.BucketVersioning("sourceVersioning", {
bucket: source.id,
versioningConfiguration: {
status: "Enabled",
},
}, {
provider: awsCentralProvider,
});
const replication = new aws.s3.BucketReplicationConfig("replication", {
role: replicationRole.arn,
bucket: source.id,
rules: [{
id: "examplerule",
filter: {
prefix: "example",
},
status: "Enabled",
destination: {
bucket: destination.arn,
storageClass: "STANDARD",
},
}],
}, {
provider: awsCentralProvider,
dependsOn: [sourceVersioning],
});

After, v7.0.0

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const assumeRoleDocument = aws.iam.getPolicyDocumentOutput({
statements: [{
effect: "Allow",
principals: [{
type: "Service",
identifiers: ["s3.amazonaws.com"],
}],
actions: ["sts:AssumeRole"],
}],
});
const awsProvider = new aws.Provider("awsProvider", {region: "eu-west-1"});
const destination = new aws.s3.Bucket("destination", {}, {
provider: awsProvider,
});
const source = new aws.s3.Bucket("source", {region: "eu-central-1"}, {
provider: awsProvider,
});
const replicationPolicyDocument = aws.iam.getPolicyDocumentOutput({
statements: [
{
effect: "Allow",
actions: [
"s3:GetReplicationConfiguration",
"s3:ListBucket",
],
resources: [source.arn],
},
{
effect: "Allow",
actions: [
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionTagging",
],
resources: [pulumi.interpolate`${source.arn}/*`],
},
{
effect: "Allow",
actions: [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags",
],
resources: [pulumi.interpolate`${destination.arn}/*`],
},
],
});
const replicationRole = new aws.iam.Role("replicationRole", {assumeRolePolicy: assumeRoleDocument.apply(assumeRoleDocument => assumeRoleDocument.json)}, {
provider: awsProvider,
});
const replicationPolicy = new aws.iam.Policy("replicationPolicy", {policy: replicationPolicyDocument.apply(replicationPolicyDocument => replicationPolicyDocument.json)}, {
provider: awsProvider,
});
const replicationPolicyAttachment = new aws.iam.RolePolicyAttachment("replicationPolicyAttachment", {
role: replicationRole.name,
policyArn: replicationPolicy.arn,
}, {
provider: awsProvider,
});
const destinationVersioning = new aws.s3.BucketVersioning("destinationVersioning", {
bucket: destination.id,
versioningConfiguration: {
status: "Enabled",
},
}, {
provider: awsProvider,
});
const sourceBucketAcl = new aws.s3.BucketAcl("sourceBucketAcl", {
region: "eu-central-1",
bucket: source.id,
acl: "private",
}, {
provider: awsProvider,
});
const sourceVersioning = new aws.s3.BucketVersioning("sourceVersioning", {
region: "eu-central-1",
bucket: source.id,
versioningConfiguration: {
status: "Enabled",
},
}, {
provider: awsProvider,
});
const replication = new aws.s3.BucketReplicationConfig("replication", {
region: "eu-central-1",
role: replicationRole.arn,
bucket: source.id,
rules: [{
id: "examplerule",
filter: {
prefix: "example",
},
status: "Enabled",
destination: {
bucket: destination.arn,
storageClass: "STANDARD",
},
}],
}, {
provider: awsProvider,
dependsOn: [sourceVersioning],
});

Non-region-aware resources#

This section lists resources that are not Region-aware–meaning region has not been added to them.

Some resources, such as IAM and STS are global and exist in all Regions within a partition.

Other resources are not Region-aware because they already had a top-level region, are inherently global, or because adding region would not be appropriate for other reasons.

Resources deprecating region#

The following regional resources and data sources had a top-level region prior to version 7.0.0. It is now deprecated and will be replaced in a future version to support the new Region-aware behavior.

  • aws.cloudformation.StackSetInstance resource
  • aws.config.AggregateAuthorization resource
  • aws.directconnect.HostedConnection resource
  • aws.getRegion function
  • aws.s3.getBucket function
  • aws.servicequotas.Template resource
  • aws.servicequotas.getTemplates function
  • aws.ssmincidents.ReplicationSet resource and aws.ssmincidents.getReplicationSet function
  • aws.ec2.getVpcEndpointService function
  • aws.ec2.getVpcPeeringConnection function

Global services#

All resources for the following services are considered global:

  • Account Management (aws.organizations.*)
  • Billing (aws.billing.*)
  • Billing and Cost Management Data Exports (aws.bcmdataexports.*)
  • Budgets (aws.budgets.*)
  • CloudFront (aws.cloudfront.* and aws.cloudfrontkeyvaluestore.*)
  • Cost Explorer (aws.ce.*)
  • Cost Optimization Hub (aws.costoptimizationhub.*)
  • Cost and Usage Report (aws.cur.*)
  • Global Accelerator (aws.globalaccelerator.*)
  • IAM (aws.iam.*, aws.rolesanywhere.* and aws.getCallerIdentity)
  • Network Manager (aws.networkmanager.*)
  • Organizations (aws.organizations.*)
  • Price List (aws.pricing.*)
  • Route 53 (aws.route53.* and aws.route53domains.*)
  • Route 53 ARC (aws.route53recoverycontrolconfig.* and aws.route53recoveryreadiness.*)
  • Shield Advanced (aws.shield.*)
  • User Notifications (aws.notifications.*)
  • User Notifications Contacts (aws.notificationscontacts.*)
  • WAF Classic (aws.waf.*)

Global resources in regional services#

Some regional services have a subset of resources that are global:

ServiceTypeName
BackupResourceaws.backup.GlobalSettings
Chime SDK VoiceResourceaws.chimesdkvoice.GlobalSettings
CloudTrailResourceaws.cloudtrail.OrganizationDelegatedAdminAccount
Direct ConnectResourceaws.directconnect.Gateway
Direct ConnectFunctionaws.directconnect.getGateway
EC2Resourceaws.ec2.ImageBlockPublicAccess
Firewall ManagerResourceaws.fms.AdminAccount
IPAMResourceaws.ec2.VpcIpamOrganizationAdminAccount
QuickSightResourceaws.quicksight.AccountSettings
Resource Access ManagerResourceaws.ram.SharingWithOrganization
S3Functionaws.s3.getCanonicalUserId
S3Resourceaws.s3.AccountPublicAccessBlock
S3Functionaws.s3.getAccountPublicAccessBlock
Service CatalogResourceaws.servicecatalog.OrganizationsAccess

Meta data sources#

The aws.getDefaultTags, aws.getPartition, and aws.getRegions functions are effectively global.

region of the aws.getArn function stays as-is.

Policy Document Data Sources#

Some data sources convert data into JSON policy documents and are effectively global:

  • aws.cloudwatch.getLogDataProtectionPolicyDocument
  • aws.ecr.getLifecyclePolicyDocument </content> </invoke>

Related

The infrastructure as code platform for any cloud.