1. Docs
  2. Infrastructure as Code
  3. Clouds
  4. AWS
  5. Guides
  6. Choosing a Provider

Choosing a Pulumi AWS Provider

    Pulumi offers several packages for working with AWS, each designed for a different level of abstraction or use case. Most projects use more than one of them together. This guide explains what each package does, how they relate to one another, and how to choose the right combination for your infrastructure.

    For most AWS infrastructure, start with the AWS provider. It is the primary, recommended choice and has the broadest coverage, documentation, and community support. Reach for the other packages to complement it when you have a specific need.

    Packages at a glance

    Providers

    AWSAWS Cloud Control
    Node.js@pulumi/aws@pulumi/aws-native
    Pythonpulumi_awspulumi_aws_native
    Gogithub.com/pulumi/pulumi-aws/sdk/v7github.com/pulumi/pulumi-aws-native/sdk/go/aws
    .NETPulumi.AwsPulumi.AwsNative
    Javacom.pulumi.awscom.pulumi.awsnative
    Built onAWS Terraform provider (via Pulumi TF bridge)AWS Cloud Control API
    Resource coverageComprehensive (200+ service modules)CloudFormation-backed (Cloud Control API types)
    Naming conventionTerraform-style (e.g., aws.s3.Bucket)CloudFormation-style (e.g., awsnative.s3.Bucket)
    Best forMost AWS infrastructureNewly launched resources; CloudFormation migration
    MaturityStableGenerally available

    Component libraries

    Component libraries build on top of the AWS provider and package common patterns into higher-level constructs. They are not separate providers — they do not have their own state and they do not replace the AWS provider.

    AWSxAWS API GatewayAmazon EKS
    Node.js@pulumi/awsx@pulumi/aws-apigateway@pulumi/eks
    Pythonpulumi_awsxpulumi_aws_apigatewaypulumi_eks
    Gogithub.com/pulumi/pulumi-awsx/sdk/v3/go/awsxgithub.com/pulumi/pulumi-aws-apigateway/sdk/v3/go/apigatewaygithub.com/pulumi/pulumi-eks/sdk/v4/go/eks
    .NETPulumi.AwsxPulumi.AwsApiGatewayPulumi.Eks
    Javacom.pulumi.awsxcom.pulumi.aws-apigatewaycom.pulumi.eks
    CoversVPC, ECS, ECR, ALB, Lambda, CloudTrailAPI Gateway REST APIsEKS clusters

    AWS provider

    The AWS provider is the primary and recommended package for managing AWS infrastructure with Pulumi. It is built on the AWS Terraform provider via the Pulumi Terraform bridge, which translates the mature HashiCorp AWS provider into native Pulumi resources, exposing a comprehensive, well-tested interface to AWS services refined by a large community over many years.

    The AWS provider covers 200+ service namespaces and supports the full breadth of AWS resources: compute, storage, networking, databases, messaging, security, and more. It follows a predictable naming convention where resource types map closely to the underlying Terraform resource names (e.g., aws.ec2.Instance, aws.s3.Bucket).

    For the vast majority of AWS infrastructure, the AWS provider should be your starting point. Its resources are thoroughly documented, support all Pulumi features (including import, state management, and drift detection), and have extensive how-to guides written for them.

    AWS Cloud Control provider

    The AWS Cloud Control provider is built directly on the AWS Cloud Control API, which AWS uses as the programmatic backing for CloudFormation. Because it derives resources directly from CloudFormation resource schemas, it can expose new AWS resource types very quickly after they launch.

    Resources in the Cloud Control provider follow CloudFormation naming conventions (PascalCase), so the equivalent of aws.s3.Bucket in the Cloud Control provider is awsnative.s3.Bucket.

    Despite broad coverage, not every AWS resource is available through the Cloud Control API, and some resources that are available have restricted operations (e.g., read-only support). For new projects, Pulumi recommends starting with the primary AWS provider and pulling in Cloud Control resources only when a specific resource is not yet available there.

    You may encounter references to the “AWS Native provider” in older blog posts, community discussions, or the @pulumi/aws-native npm package name. This provider was renamed to the AWS Cloud Control provider when it reached general availability, reflecting its underlying use of the AWS Cloud Control API. The npm package name and registry path remain unchanged to avoid breaking existing users.

    Component libraries

    AWSx

    AWSx is a higher-level component library that sits on top of the AWS provider and packages common AWS patterns into opinionated, reusable constructs. Rather than manually assembling many individual resources, AWSx gives you a single component with sensible defaults that follow AWS well-architected practices.

    AWSx includes components for VPC (subnets, route tables, NAT gateways, internet gateways), ECS clusters and Fargate services, ECR repositories, load balancers (ALB/NLB), Lambda functions, and CloudTrail trails.

    Because AWSx builds on top of the AWS provider, you can inspect every underlying resource it creates, override its defaults, and use it alongside standard aws.* resources in the same stack without any conflict.

    AWS API Gateway

    AWS API Gateway provides higher-level components for defining and deploying Amazon API Gateway REST APIs. It handles the boilerplate of API Gateway stage, deployment, and integration configuration, letting you focus on the routes and business logic of your API.

    Amazon EKS

    Amazon EKS provides components for creating and managing Amazon Elastic Kubernetes Service clusters. It handles the complexity of node groups, VPC configuration, IAM roles, and add-ons, making it significantly easier to stand up a production-ready EKS cluster.

    Pulumi CDK adapter

    The Pulumi CDK adapter allows you to use AWS CDK constructs — including CDK’s L2 and L3 constructs — directly within a Pulumi program. The most common scenarios for reaching for it are:

    • Your organization has existing CDK constructs you want to reuse during a migration to Pulumi.
    • A specific CDK construct library solves a problem for which there is no equivalent Pulumi component yet, and you want to leverage it without waiting for a native replacement.

    For new infrastructure, use the native providers and component libraries. Most organizations that adopt the CDK adapter do so as a transitional step rather than a long-term strategy.

    See the Pulumi CDK guide for more detail.

    Choosing the right package

    Start with the AWS provider

    For any new project on AWS, begin with the AWS provider. It has the broadest community support, the most extensive documentation and examples, and the most predictable behavior. If the resource you need exists in the AWS provider, use it there.

    When to reach for AWS Cloud Control

    Reach for the AWS Cloud Control provider when:

    • A newly launched AWS service or resource type is not yet available in the classic AWS provider.
    • You are migrating an existing CloudFormation template to Pulumi and want resource types that map closely to their CloudFormation equivalents, with matching property names and structure.
    • A resource type you need is only available through the Cloud Control API.

    Because the Cloud Control API itself is still maturing, some resource types have limited operation support. See the AWS Cloud Control provider API docs for the full list of supported resources before designing your stack around a specific resource.

    When to use component libraries

    Use the component libraries (AWSx, AWS API Gateway, Amazon EKS) when you need to stand up well-established AWS patterns quickly and want Pulumi to handle the low-level resource wiring. They are particularly valuable for teams who want sensible defaults without becoming experts in every service’s configuration details.

    For teams with strong infrastructure opinions or custom requirements — for example, an organization with specific VPC design standards — you may prefer to use the AWS provider directly and build your own Pulumi components. The component libraries and the AWS provider work well alongside one another in the same stack, so you can adopt them selectively.

    Pulumi CDK adapter

    Reach for the Pulumi CDK adapter when your team has existing CDK constructs you want to reuse during a migration to Pulumi, or when a specific CDK construct library solves a problem for which there is no equivalent Pulumi component yet. It works best as a transitional tool rather than a primary strategy — most teams that use it are working toward replacing CDK constructs with native Pulumi components over time. For new infrastructure, use the native providers and component libraries.

    Using multiple packages in a single stack

    All of Pulumi’s AWS packages can be used together in a single stack. Because Pulumi tracks all resources in a unified state, there is no penalty for mixing providers or component libraries. A common pattern is to use AWSx for networking and container infrastructure, the classic AWS provider for the majority of other resources, and the Cloud Control provider selectively for newer resource types not yet available elsewhere.

    The following example demonstrates this pattern, using AWSx for the VPC, the AWS provider for an S3 bucket, and the Cloud Control provider for an Amazon Application Signals SLO — a newer resource type available through Cloud Control but not yet in the classic provider:

    import * as aws from "@pulumi/aws";
    import * as awsnative from "@pulumi/aws-native";
    import * as awsx from "@pulumi/awsx";
    
    // AWSx provides a VPC with well-architected defaults: subnets, route
    // tables, NAT gateways, and an internet gateway, all pre-configured.
    const vpc = new awsx.ec2.Vpc("main", {
        natGateways: { strategy: "Single" },
    });
    
    // The AWS provider manages the majority of resources.
    const bucket = new aws.s3.Bucket("app-data");
    
    // The AWS Cloud Control provider is used for a resource available
    // through Cloud Control but not yet in the classic AWS provider.
    const slo = new awsnative.applicationsignals.ServiceLevelObjective("api-slo", {
        name: "api-availability-slo",
        sli: {
            sliMetric: {
                metricType: "AVAILABILITY",
                operationName: "GET /api",
                keyAttributes: {
                    Type: "Service",
                    Name: "my-service",
                    Environment: "production",
                },
            },
            comparisonOperator: "GreaterThanOrEqualTo",
            metricThreshold: 99,
        },
        goal: {
            attainmentGoal: 99,
            warningThreshold: 99.5,
            interval: {
                rollingInterval: { durationUnit: "DAY", duration: 30 },
            },
        },
    });
    
    export const vpcId = vpc.vpcId;
    export const bucketName = bucket.bucket;
    export const sloArn = slo.arn;
    import pulumi
    import pulumi_aws as aws
    import pulumi_aws_native as awsnative
    import pulumi_awsx as awsx
    
    # AWSx provides a VPC with well-architected defaults: subnets, route
    # tables, NAT gateways, and an internet gateway, all pre-configured.
    vpc = awsx.ec2.Vpc("main", nat_gateways=awsx.ec2.NatGatewayConfigurationArgs(
        strategy=awsx.ec2.NatGatewayStrategy.SINGLE,
    ))
    
    # The AWS provider manages the majority of resources.
    bucket = aws.s3.Bucket("app-data")
    
    # The AWS Cloud Control provider is used for a resource available
    # through Cloud Control but not yet in the classic AWS provider.
    slo = awsnative.applicationsignals.ServiceLevelObjective("api-slo",
        name="api-availability-slo",
        sli=awsnative.applicationsignals.ServiceLevelObjectiveSliArgs(
            sli_metric=awsnative.applicationsignals.ServiceLevelObjectiveSliMetricArgs(
                metric_type=awsnative.applicationsignals.ServiceLevelObjectiveSliMetricMetricType.AVAILABILITY,
                operation_name="GET /api",
                key_attributes={
                    "Type": "Service",
                    "Name": "my-service",
                    "Environment": "production",
                },
            ),
            comparison_operator=awsnative.applicationsignals.ServiceLevelObjectiveSliComparisonOperator.GREATER_THAN_OR_EQUAL_TO,
            metric_threshold=99,
        ),
        goal=awsnative.applicationsignals.ServiceLevelObjectiveGoalArgs(
            attainment_goal=99,
            warning_threshold=99.5,
            interval=awsnative.applicationsignals.ServiceLevelObjectiveIntervalArgs(
                rolling_interval=awsnative.applicationsignals.ServiceLevelObjectiveRollingIntervalArgs(
                    duration_unit=awsnative.applicationsignals.ServiceLevelObjectiveDurationUnit.DAY,
                    duration=30,
                ),
            ),
        ),
    )
    
    pulumi.export("vpcId", vpc.vpc_id)
    pulumi.export("bucketName", bucket.bucket)
    pulumi.export("sloArn", slo.arn)
    package main
    
    import (
    	"github.com/pulumi/pulumi-aws-native/sdk/go/aws/applicationsignals"
    	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
    	"github.com/pulumi/pulumi-awsx/sdk/v3/go/awsx/ec2"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    
    		// AWSx provides a VPC with well-architected defaults: subnets, route
    		// tables, NAT gateways, and an internet gateway, all pre-configured.
    		vpc, err := ec2.NewVpc(ctx, "main", &ec2.VpcArgs{
    			NatGateways: &ec2.NatGatewayConfigurationArgs{
    				Strategy: ec2.NatGatewayStrategySingle,
    			},
    		})
    		if err != nil {
    			return err
    		}
    
    		// The AWS provider manages the majority of resources.
    		bucket, err := s3.NewBucket(ctx, "app-data", nil)
    		if err != nil {
    			return err
    		}
    
    		// The AWS Cloud Control provider is used for a resource available
    		// through Cloud Control but not yet in the classic AWS provider.
    		slo, err := applicationsignals.NewServiceLevelObjective(ctx, "api-slo", &applicationsignals.ServiceLevelObjectiveArgs{
    			Name: pulumi.String("api-availability-slo"),
    			Sli: applicationsignals.ServiceLevelObjectiveSliArgs{
    				SliMetric: applicationsignals.ServiceLevelObjectiveSliMetricArgs{
    					MetricType:    applicationsignals.ServiceLevelObjectiveSliMetricMetricTypeAvailability,
    					OperationName: pulumi.StringPtr("GET /api"),
    					KeyAttributes: pulumi.StringMap{
    						"Type":        pulumi.String("Service"),
    						"Name":        pulumi.String("my-service"),
    						"Environment": pulumi.String("production"),
    					},
    				},
    				ComparisonOperator: applicationsignals.ServiceLevelObjectiveSliComparisonOperatorGreaterThanOrEqualTo,
    				MetricThreshold:    pulumi.Float64(99),
    			},
    			Goal: &applicationsignals.ServiceLevelObjectiveGoalArgs{
    				AttainmentGoal:   pulumi.Float64Ptr(99),
    				WarningThreshold: pulumi.Float64Ptr(99.5),
    				Interval: applicationsignals.ServiceLevelObjectiveIntervalArgs{
    					RollingInterval: &applicationsignals.ServiceLevelObjectiveRollingIntervalArgs{
    						DurationUnit: applicationsignals.ServiceLevelObjectiveDurationUnitDay,
    						Duration:     pulumi.Int(30),
    					},
    				},
    			},
    		})
    		if err != nil {
    			return err
    		}
    
    		ctx.Export("vpcId", vpc.VpcId)
    		ctx.Export("bucketName", bucket.Bucket)
    		ctx.Export("sloArn", slo.Arn)
    
    		return nil
    	})
    }
    
    using Pulumi;
    using System.Collections.Generic;
    using Pulumi.Awsx.Ec2;
    using Pulumi.Awsx.Ec2.Inputs;
    using Aws = Pulumi.Aws;
    using AwsNative = Pulumi.AwsNative;
    
    return await Deployment.RunAsync(() =>
    {
        // AWSx provides a VPC with well-architected defaults: subnets, route
        // tables, NAT gateways, and an internet gateway, all pre-configured.
        var vpc = new Vpc("main", new VpcArgs
        {
            NatGateways = new NatGatewayConfigurationArgs
            {
                Strategy = NatGatewayStrategy.Single,
            },
        });
    
        // The AWS provider manages the majority of resources.
        var bucket = new Aws.S3.Bucket("app-data");
    
        // The AWS Cloud Control provider is used for a resource available
        // through Cloud Control but not yet in the classic AWS provider.
        var slo = new AwsNative.ApplicationSignals.ServiceLevelObjective("api-slo", new()
        {
            Name = "api-availability-slo",
            Sli = new AwsNative.ApplicationSignals.Inputs.ServiceLevelObjectiveSliArgs
            {
                SliMetric = new AwsNative.ApplicationSignals.Inputs.ServiceLevelObjectiveSliMetricArgs
                {
                    MetricType = AwsNative.ApplicationSignals.ServiceLevelObjectiveSliMetricMetricType.Availability,
                    OperationName = "GET /api",
                    KeyAttributes =
                    {
                        ["Type"] = "Service",
                        ["Name"] = "my-service",
                        ["Environment"] = "production",
                    },
                },
                ComparisonOperator = AwsNative.ApplicationSignals.ServiceLevelObjectiveSliComparisonOperator.GreaterThanOrEqualTo,
                MetricThreshold = 99,
            },
            Goal = new AwsNative.ApplicationSignals.Inputs.ServiceLevelObjectiveGoalArgs
            {
                AttainmentGoal = 99,
                WarningThreshold = 99.5,
                Interval = new AwsNative.ApplicationSignals.Inputs.ServiceLevelObjectiveIntervalArgs
                {
                    RollingInterval = new AwsNative.ApplicationSignals.Inputs.ServiceLevelObjectiveRollingIntervalArgs
                    {
                        DurationUnit = AwsNative.ApplicationSignals.ServiceLevelObjectiveDurationUnit.Day,
                        Duration = 30,
                    },
                },
            },
        });
    
        return new Dictionary<string, object?>
        {
            ["vpcId"] = vpc.VpcId,
            ["bucketName"] = bucket.Id,
            ["sloArn"] = slo.Arn,
        };
    });
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.awsx.ec2.Vpc;
    import com.pulumi.awsx.ec2.VpcArgs;
    import com.pulumi.awsx.ec2.inputs.NatGatewayConfigurationArgs;
    import com.pulumi.awsx.ec2.enums.NatGatewayStrategy;
    import com.pulumi.aws.s3.Bucket;
    import com.pulumi.awsnative.applicationsignals.ServiceLevelObjective;
    import com.pulumi.awsnative.applicationsignals.ServiceLevelObjectiveArgs;
    import com.pulumi.awsnative.applicationsignals.inputs.ServiceLevelObjectiveSliArgs;
    import com.pulumi.awsnative.applicationsignals.inputs.ServiceLevelObjectiveSliMetricArgs;
    import com.pulumi.awsnative.applicationsignals.inputs.ServiceLevelObjectiveGoalArgs;
    import com.pulumi.awsnative.applicationsignals.inputs.ServiceLevelObjectiveIntervalArgs;
    import com.pulumi.awsnative.applicationsignals.inputs.ServiceLevelObjectiveRollingIntervalArgs;
    import com.pulumi.awsnative.applicationsignals.enums.ServiceLevelObjectiveSliMetricMetricType;
    import com.pulumi.awsnative.applicationsignals.enums.ServiceLevelObjectiveSliComparisonOperator;
    import com.pulumi.awsnative.applicationsignals.enums.ServiceLevelObjectiveDurationUnit;
    import java.util.Map;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
    
                // AWSx provides a VPC with well-architected defaults: subnets, route
                // tables, NAT gateways, and an internet gateway, all pre-configured.
                var vpc = new Vpc("main", VpcArgs.builder()
                    .natGateways(NatGatewayConfigurationArgs.builder()
                        .strategy(NatGatewayStrategy.Single)
                        .build())
                    .build());
    
                // The AWS provider manages the majority of resources.
                var bucket = new Bucket("app-data");
    
                // The AWS Cloud Control provider is used for a resource available
                // through Cloud Control but not yet in the classic AWS provider.
                var slo = new ServiceLevelObjective("api-slo", ServiceLevelObjectiveArgs.builder()
                    .name("api-availability-slo")
                    .sli(ServiceLevelObjectiveSliArgs.builder()
                        .sliMetric(ServiceLevelObjectiveSliMetricArgs.builder()
                            .metricType(ServiceLevelObjectiveSliMetricMetricType.Availability)
                            .operationName("GET /api")
                            .keyAttributes(Map.of(
                                "Type", "Service",
                                "Name", "my-service",
                                "Environment", "production"))
                            .build())
                        .comparisonOperator(ServiceLevelObjectiveSliComparisonOperator.GreaterThanOrEqualTo)
                        .metricThreshold(99.0)
                        .build())
                    .goal(ServiceLevelObjectiveGoalArgs.builder()
                        .attainmentGoal(99.0)
                        .warningThreshold(99.5)
                        .interval(ServiceLevelObjectiveIntervalArgs.builder()
                            .rollingInterval(ServiceLevelObjectiveRollingIntervalArgs.builder()
                                .durationUnit(ServiceLevelObjectiveDurationUnit.Day)
                                .duration(30)
                                .build())
                            .build())
                        .build())
                    .build());
    
                ctx.export("vpcId", vpc.vpcId());
                ctx.export("bucketName", bucket.bucket());
                ctx.export("sloArn", slo.arn());
            });
        }
    }
    name: aws-providers-combine-yaml
    runtime: yaml
    
    resources:
      # AWSx provides a VPC with well-architected defaults: subnets, route
      # tables, NAT gateways, and an internet gateway, all pre-configured.
      main:
        type: awsx:ec2:Vpc
        properties:
          natGateways:
            strategy: Single
    
      # The AWS provider manages the majority of resources.
      app-data:
        type: aws:s3:Bucket
    
      # The AWS Cloud Control provider is used for a resource available
      # through Cloud Control but not yet in the classic AWS provider.
      api-slo:
        type: aws-native:applicationsignals:ServiceLevelObjective
        properties:
          name: api-availability-slo
          sli:
            sliMetric:
              metricType: AVAILABILITY
              operationName: GET /api
              keyAttributes:
                Type: Service
                Name: my-service
                Environment: production
            comparisonOperator: GreaterThanOrEqualTo
            metricThreshold: 99
          goal:
            attainmentGoal: 99
            warningThreshold: 99.5
            interval:
              rollingInterval:
                durationUnit: DAY
                duration: 30
    
    outputs:
      vpcId: ${main.vpcId}
      bucketName: ${app-data.bucket}
      sloArn: ${api-slo.arn}
    

    When you run pulumi up, Pulumi’s engine coordinates all providers, resolves cross-resource dependencies, and records the combined state in your Pulumi Cloud backend.

    Next steps