Deploy AWS CloudFormation Stack Instances

The aws:cloudformation/stackInstances:StackInstances resource, part of the Pulumi AWS provider, manages the deployment of CloudFormation stacks to specific accounts and regions from a StackSet. This guide focuses on three capabilities: account-based targeting, organizational unit targeting, and IAM execution role setup.

Stack instances require an existing StackSet and IAM execution roles in all target accounts. The examples are intentionally small. Combine them with your own StackSet definitions and organizational structure.

Configure execution role in target accounts

Before deploying stack instances, each target account needs an IAM execution role that trusts the administrator account and has permissions to create the resources defined in your CloudFormation template.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy = aws.iam.getPolicyDocument({
    statements: [{
        actions: ["sts:AssumeRole"],
        effect: "Allow",
        principals: [{
            identifiers: [aWSCloudFormationStackSetAdministrationRole.arn],
            type: "AWS",
        }],
    }],
});
const aWSCloudFormationStackSetExecutionRole = new aws.iam.Role("AWSCloudFormationStackSetExecutionRole", {
    assumeRolePolicy: aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy.then(aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy => aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy.json),
    name: "AWSCloudFormationStackSetExecutionRole",
});
// Documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html
// Additional IAM permissions necessary depend on the resources defined in the StackSet template
const aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy = aws.iam.getPolicyDocument({
    statements: [{
        actions: [
            "cloudformation:*",
            "s3:*",
            "sns:*",
        ],
        effect: "Allow",
        resources: ["*"],
    }],
});
const aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicyRolePolicy = new aws.iam.RolePolicy("AWSCloudFormationStackSetExecutionRole_MinimumExecutionPolicy", {
    name: "MinimumExecutionPolicy",
    policy: aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy.then(aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy => aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy.json),
    role: aWSCloudFormationStackSetExecutionRole.name,
});
import pulumi
import pulumi_aws as aws

a_ws_cloud_formation_stack_set_execution_role_assume_role_policy = aws.iam.get_policy_document(statements=[{
    "actions": ["sts:AssumeRole"],
    "effect": "Allow",
    "principals": [{
        "identifiers": [a_ws_cloud_formation_stack_set_administration_role["arn"]],
        "type": "AWS",
    }],
}])
a_ws_cloud_formation_stack_set_execution_role = aws.iam.Role("AWSCloudFormationStackSetExecutionRole",
    assume_role_policy=a_ws_cloud_formation_stack_set_execution_role_assume_role_policy.json,
    name="AWSCloudFormationStackSetExecutionRole")
# Documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html
# Additional IAM permissions necessary depend on the resources defined in the StackSet template
a_ws_cloud_formation_stack_set_execution_role_minimum_execution_policy = aws.iam.get_policy_document(statements=[{
    "actions": [
        "cloudformation:*",
        "s3:*",
        "sns:*",
    ],
    "effect": "Allow",
    "resources": ["*"],
}])
a_ws_cloud_formation_stack_set_execution_role_minimum_execution_policy_role_policy = aws.iam.RolePolicy("AWSCloudFormationStackSetExecutionRole_MinimumExecutionPolicy",
    name="MinimumExecutionPolicy",
    policy=a_ws_cloud_formation_stack_set_execution_role_minimum_execution_policy.json,
    role=a_ws_cloud_formation_stack_set_execution_role.name)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Actions: []string{
"sts:AssumeRole",
},
Effect: pulumi.StringRef("Allow"),
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Identifiers: interface{}{
aWSCloudFormationStackSetAdministrationRole.Arn,
},
Type: "AWS",
},
},
},
},
}, nil);
if err != nil {
return err
}
aWSCloudFormationStackSetExecutionRole, err := iam.NewRole(ctx, "AWSCloudFormationStackSetExecutionRole", &iam.RoleArgs{
AssumeRolePolicy: pulumi.String(aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy.Json),
Name: pulumi.String("AWSCloudFormationStackSetExecutionRole"),
})
if err != nil {
return err
}
// Documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html
// Additional IAM permissions necessary depend on the resources defined in the StackSet template
aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Actions: []string{
"cloudformation:*",
"s3:*",
"sns:*",
},
Effect: pulumi.StringRef("Allow"),
Resources: []string{
"*",
},
},
},
}, nil);
if err != nil {
return err
}
_, err = iam.NewRolePolicy(ctx, "AWSCloudFormationStackSetExecutionRole_MinimumExecutionPolicy", &iam.RolePolicyArgs{
Name: pulumi.String("MinimumExecutionPolicy"),
Policy: pulumi.String(aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy.Json),
Role: aWSCloudFormationStackSetExecutionRole.Name,
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy = Aws.Iam.GetPolicyDocument.Invoke(new()
    {
        Statements = new[]
        {
            new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
            {
                Actions = new[]
                {
                    "sts:AssumeRole",
                },
                Effect = "Allow",
                Principals = new[]
                {
                    new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
                    {
                        Identifiers = new[]
                        {
                            aWSCloudFormationStackSetAdministrationRole.Arn,
                        },
                        Type = "AWS",
                    },
                },
            },
        },
    });

    var aWSCloudFormationStackSetExecutionRole = new Aws.Iam.Role("AWSCloudFormationStackSetExecutionRole", new()
    {
        AssumeRolePolicy = aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
        Name = "AWSCloudFormationStackSetExecutionRole",
    });

    // Documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html
    // Additional IAM permissions necessary depend on the resources defined in the StackSet template
    var aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy = Aws.Iam.GetPolicyDocument.Invoke(new()
    {
        Statements = new[]
        {
            new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
            {
                Actions = new[]
                {
                    "cloudformation:*",
                    "s3:*",
                    "sns:*",
                },
                Effect = "Allow",
                Resources = new[]
                {
                    "*",
                },
            },
        },
    });

    var aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicyRolePolicy = new Aws.Iam.RolePolicy("AWSCloudFormationStackSetExecutionRole_MinimumExecutionPolicy", new()
    {
        Name = "MinimumExecutionPolicy",
        Policy = aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
        Role = aWSCloudFormationStackSetExecutionRole.Name,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import com.pulumi.aws.iam.RolePolicy;
import com.pulumi.aws.iam.RolePolicyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        final var aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
            .statements(GetPolicyDocumentStatementArgs.builder()
                .actions("sts:AssumeRole")
                .effect("Allow")
                .principals(GetPolicyDocumentStatementPrincipalArgs.builder()
                    .identifiers(aWSCloudFormationStackSetAdministrationRole.arn())
                    .type("AWS")
                    .build())
                .build())
            .build());

        var aWSCloudFormationStackSetExecutionRole = new Role("aWSCloudFormationStackSetExecutionRole", RoleArgs.builder()
            .assumeRolePolicy(aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy.json())
            .name("AWSCloudFormationStackSetExecutionRole")
            .build());

        // Documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html
        // Additional IAM permissions necessary depend on the resources defined in the StackSet template
        final var aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
            .statements(GetPolicyDocumentStatementArgs.builder()
                .actions(                
                    "cloudformation:*",
                    "s3:*",
                    "sns:*")
                .effect("Allow")
                .resources("*")
                .build())
            .build());

        var aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicyRolePolicy = new RolePolicy("aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicyRolePolicy", RolePolicyArgs.builder()
            .name("MinimumExecutionPolicy")
            .policy(aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy.json())
            .role(aWSCloudFormationStackSetExecutionRole.name())
            .build());

    }
}
resources:
  aWSCloudFormationStackSetExecutionRole:
    type: aws:iam:Role
    name: AWSCloudFormationStackSetExecutionRole
    properties:
      assumeRolePolicy: ${aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy.json}
      name: AWSCloudFormationStackSetExecutionRole
  aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicyRolePolicy:
    type: aws:iam:RolePolicy
    name: AWSCloudFormationStackSetExecutionRole_MinimumExecutionPolicy
    properties:
      name: MinimumExecutionPolicy
      policy: ${aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy.json}
      role: ${aWSCloudFormationStackSetExecutionRole.name}
variables:
  aWSCloudFormationStackSetExecutionRoleAssumeRolePolicy:
    fn::invoke:
      function: aws:iam:getPolicyDocument
      arguments:
        statements:
          - actions:
              - sts:AssumeRole
            effect: Allow
            principals:
              - identifiers:
                  - ${aWSCloudFormationStackSetAdministrationRole.arn}
                type: AWS
  # Documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html
  # Additional IAM permissions necessary depend on the resources defined in the StackSet template
  aWSCloudFormationStackSetExecutionRoleMinimumExecutionPolicy:
    fn::invoke:
      function: aws:iam:getPolicyDocument
      arguments:
        statements:
          - actions:
              - cloudformation:*
              - s3:*
              - sns:*
            effect: Allow
            resources:
              - '*'

The execution role must trust the administrator role via its assumeRolePolicy, allowing CloudFormation to assume it during deployment. The role’s permissions must cover all resources your template creates. The example shows minimum CloudFormation permissions; you’ll need to add permissions for any AWS services your template uses (EC2, RDS, Lambda, etc.).

Deploy stack instances to specific accounts and regions

Most StackSet deployments target a known list of AWS accounts across multiple regions, creating identical infrastructure in each location.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.cloudformation.StackInstances("example", {
    accounts: [
        "123456789012",
        "234567890123",
    ],
    regions: [
        "us-east-1",
        "us-west-2",
    ],
    stackSetName: exampleAwsCloudformationStackSet.name,
});
import pulumi
import pulumi_aws as aws

example = aws.cloudformation.StackInstances("example",
    accounts=[
        "123456789012",
        "234567890123",
    ],
    regions=[
        "us-east-1",
        "us-west-2",
    ],
    stack_set_name=example_aws_cloudformation_stack_set["name"])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudformation"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cloudformation.NewStackInstances(ctx, "example", &cloudformation.StackInstancesArgs{
			Accounts: pulumi.StringArray{
				pulumi.String("123456789012"),
				pulumi.String("234567890123"),
			},
			Regions: pulumi.StringArray{
				pulumi.String("us-east-1"),
				pulumi.String("us-west-2"),
			},
			StackSetName: pulumi.Any(exampleAwsCloudformationStackSet.Name),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.CloudFormation.StackInstances("example", new()
    {
        Accounts = new[]
        {
            "123456789012",
            "234567890123",
        },
        Regions = new[]
        {
            "us-east-1",
            "us-west-2",
        },
        StackSetName = exampleAwsCloudformationStackSet.Name,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudformation.StackInstances;
import com.pulumi.aws.cloudformation.StackInstancesArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new StackInstances("example", StackInstancesArgs.builder()
            .accounts(            
                "123456789012",
                "234567890123")
            .regions(            
                "us-east-1",
                "us-west-2")
            .stackSetName(exampleAwsCloudformationStackSet.name())
            .build());

    }
}
resources:
  example:
    type: aws:cloudformation:StackInstances
    properties:
      accounts:
        - '123456789012'
        - '234567890123'
      regions:
        - us-east-1
        - us-west-2
      stackSetName: ${exampleAwsCloudformationStackSet.name}

CloudFormation creates one stack instance per account-region pair. The accounts property lists target AWS account IDs; regions specifies where to deploy in each account. The stackSetName references your existing StackSet definition. All target accounts must have the execution role configured before deployment succeeds.

Deploy to organizational units automatically

Organizations with many accounts often deploy to entire organizational units rather than maintaining explicit account lists, allowing new accounts to inherit stacks automatically.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.cloudformation.StackInstances("example", {
    deploymentTargets: {
        organizationalUnitIds: [exampleAwsOrganizationsOrganization.roots[0].id],
    },
    regions: [
        "us-west-2",
        "us-east-1",
    ],
    stackSetName: exampleAwsCloudformationStackSet.name,
});
import pulumi
import pulumi_aws as aws

example = aws.cloudformation.StackInstances("example",
    deployment_targets={
        "organizational_unit_ids": [example_aws_organizations_organization["roots"][0]["id"]],
    },
    regions=[
        "us-west-2",
        "us-east-1",
    ],
    stack_set_name=example_aws_cloudformation_stack_set["name"])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudformation"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cloudformation.NewStackInstances(ctx, "example", &cloudformation.StackInstancesArgs{
			DeploymentTargets: &cloudformation.StackInstancesDeploymentTargetsArgs{
				OrganizationalUnitIds: pulumi.StringArray{
					exampleAwsOrganizationsOrganization.Roots[0].Id,
				},
			},
			Regions: pulumi.StringArray{
				pulumi.String("us-west-2"),
				pulumi.String("us-east-1"),
			},
			StackSetName: pulumi.Any(exampleAwsCloudformationStackSet.Name),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.CloudFormation.StackInstances("example", new()
    {
        DeploymentTargets = new Aws.CloudFormation.Inputs.StackInstancesDeploymentTargetsArgs
        {
            OrganizationalUnitIds = new[]
            {
                exampleAwsOrganizationsOrganization.Roots[0].Id,
            },
        },
        Regions = new[]
        {
            "us-west-2",
            "us-east-1",
        },
        StackSetName = exampleAwsCloudformationStackSet.Name,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudformation.StackInstances;
import com.pulumi.aws.cloudformation.StackInstancesArgs;
import com.pulumi.aws.cloudformation.inputs.StackInstancesDeploymentTargetsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new StackInstances("example", StackInstancesArgs.builder()
            .deploymentTargets(StackInstancesDeploymentTargetsArgs.builder()
                .organizationalUnitIds(exampleAwsOrganizationsOrganization.roots()[0].id())
                .build())
            .regions(            
                "us-west-2",
                "us-east-1")
            .stackSetName(exampleAwsCloudformationStackSet.name())
            .build());

    }
}
resources:
  example:
    type: aws:cloudformation:StackInstances
    properties:
      deploymentTargets:
        organizationalUnitIds:
          - ${exampleAwsOrganizationsOrganization.roots[0].id}
      regions:
        - us-west-2
        - us-east-1
      stackSetName: ${exampleAwsCloudformationStackSet.name}

The deploymentTargets property replaces explicit account lists with organizational unit IDs. CloudFormation automatically discovers all accounts in the specified OUs and deploys to them. New accounts added to the OU later will receive stack instances automatically. This approach scales better than account-based targeting for large organizations.

Beyond these examples

These snippets focus on specific stack instance features: account-based and OU-based targeting, and IAM execution role configuration. They’re intentionally minimal rather than full multi-account deployment solutions.

The examples require pre-existing infrastructure such as CloudFormation StackSet, AWS Organizations structure (for OU targeting), and IAM administrator role in management account. They focus on configuring stack instances rather than provisioning the StackSet or organizational structure.

To keep things focused, common stack instance patterns are omitted, including:

  • Parameter overrides per instance (parameterOverrides)
  • Operation preferences (failure tolerance, parallelism)
  • Stack retention on resource deletion (retainStacks)
  • Delegated administrator configuration (callAs)

These omissions are intentional: the goal is to illustrate how stack instance targeting is wired, not provide drop-in multi-account modules. See the CloudFormation StackInstances resource reference for all available configuration options.

Let's deploy AWS CloudFormation Stack Instances

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Drift Detection
Why does Pulumi keep showing changes to my stack instances?
This resource manages all stack instances for the specified stack set. If you create instances outside Pulumi or import existing infrastructure, you must include all accounts and regions in your configuration. Missing any accounts or regions causes Pulumi to continuously report differences.
Why isn't drift detection working for my parameter overrides?
Drift detection for parameterOverrides is limited to the first account and region only, since each instance can have unique parameters.
Can drift detection track changes to deployment targets?
No, drift detection is not possible for most of the deploymentTargets argument.
IAM & Permissions
What IAM role do I need in target accounts?

Each target account must have an IAM role matching the executionRoleName from your stack set. This role needs:

  1. A trust relationship with the administrative account or admin IAM role
  2. Permissions to manage resources defined in your template
  3. CloudFormation stack operation permissions

See the IAM setup example for a complete configuration.

Deployment Targets
What's the difference between accounts and deploymentTargets?
Use accounts to specify individual AWS account IDs, or use deploymentTargets to target AWS Organizations organizational units. You can specify either accounts or deploymentTargets, but not both.
Can I deploy stack instances to the organization management account?
No, stack sets don’t deploy instances to the organization management account, even if it’s included in an OU in your deploymentTargets.
Lifecycle & Retention
How do I prevent stack deletion when destroying this resource?
Set retainStacks to true and apply it successfully before running the destroy operation. This must be done in a separate apply, not in the same operation that destroys the resource.
Can I change the stack set name after creation?
No, stackSetName is immutable and cannot be changed after the resource is created.

Using a different cloud?

Explore integration guides for other cloud providers: