Create AWS RDS Aurora Clusters

The aws:rds/cluster:Cluster resource, part of the Pulumi AWS provider, defines an RDS Aurora cluster or Multi-AZ DB cluster: its engine type, availability zones, backup configuration, and scaling behavior. This guide focuses on four capabilities: Aurora MySQL and PostgreSQL clusters, Multi-AZ clusters with provisioned IOPS, Serverless v2 auto-scaling, and Secrets Manager password management.

RDS clusters depend on VPC infrastructure and are typically paired with ClusterInstance resources to define compute capacity. The examples are intentionally small. Combine them with your own VPC configuration, security groups, and instance definitions.

Create an Aurora MySQL cluster with backups

Most Aurora deployments start with a basic cluster that specifies the engine, availability zones, and backup settings.

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

const _default = new aws.rds.Cluster("default", {
    clusterIdentifier: "aurora-cluster-demo",
    engine: aws.rds.EngineType.AuroraMysql,
    engineVersion: "5.7.mysql_aurora.2.03.2",
    availabilityZones: [
        "us-west-2a",
        "us-west-2b",
        "us-west-2c",
    ],
    databaseName: "mydb",
    masterUsername: "foo",
    masterPassword: "must_be_eight_characters",
    backupRetentionPeriod: 5,
    preferredBackupWindow: "07:00-09:00",
});
import pulumi
import pulumi_aws as aws

default = aws.rds.Cluster("default",
    cluster_identifier="aurora-cluster-demo",
    engine=aws.rds.EngineType.AURORA_MYSQL,
    engine_version="5.7.mysql_aurora.2.03.2",
    availability_zones=[
        "us-west-2a",
        "us-west-2b",
        "us-west-2c",
    ],
    database_name="mydb",
    master_username="foo",
    master_password="must_be_eight_characters",
    backup_retention_period=5,
    preferred_backup_window="07:00-09:00")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := rds.NewCluster(ctx, "default", &rds.ClusterArgs{
			ClusterIdentifier: pulumi.String("aurora-cluster-demo"),
			Engine:            pulumi.String(rds.EngineTypeAuroraMysql),
			EngineVersion:     pulumi.String("5.7.mysql_aurora.2.03.2"),
			AvailabilityZones: pulumi.StringArray{
				pulumi.String("us-west-2a"),
				pulumi.String("us-west-2b"),
				pulumi.String("us-west-2c"),
			},
			DatabaseName:          pulumi.String("mydb"),
			MasterUsername:        pulumi.String("foo"),
			MasterPassword:        pulumi.String("must_be_eight_characters"),
			BackupRetentionPeriod: pulumi.Int(5),
			PreferredBackupWindow: pulumi.String("07:00-09:00"),
		})
		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 @default = new Aws.Rds.Cluster("default", new()
    {
        ClusterIdentifier = "aurora-cluster-demo",
        Engine = Aws.Rds.EngineType.AuroraMysql,
        EngineVersion = "5.7.mysql_aurora.2.03.2",
        AvailabilityZones = new[]
        {
            "us-west-2a",
            "us-west-2b",
            "us-west-2c",
        },
        DatabaseName = "mydb",
        MasterUsername = "foo",
        MasterPassword = "must_be_eight_characters",
        BackupRetentionPeriod = 5,
        PreferredBackupWindow = "07:00-09:00",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.rds.Cluster;
import com.pulumi.aws.rds.ClusterArgs;
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 default_ = new Cluster("default", ClusterArgs.builder()
            .clusterIdentifier("aurora-cluster-demo")
            .engine("aurora-mysql")
            .engineVersion("5.7.mysql_aurora.2.03.2")
            .availabilityZones(            
                "us-west-2a",
                "us-west-2b",
                "us-west-2c")
            .databaseName("mydb")
            .masterUsername("foo")
            .masterPassword("must_be_eight_characters")
            .backupRetentionPeriod(5)
            .preferredBackupWindow("07:00-09:00")
            .build());

    }
}
resources:
  default:
    type: aws:rds:Cluster
    properties:
      clusterIdentifier: aurora-cluster-demo
      engine: aurora-mysql
      engineVersion: 5.7.mysql_aurora.2.03.2
      availabilityZones:
        - us-west-2a
        - us-west-2b
        - us-west-2c
      databaseName: mydb
      masterUsername: foo
      masterPassword: must_be_eight_characters
      backupRetentionPeriod: 5
      preferredBackupWindow: 07:00-09:00

The engine and engineVersion properties select Aurora MySQL 5.7. The availabilityZones array distributes storage across three zones for high availability. The backupRetentionPeriod and preferredBackupWindow properties control automated backups: this cluster retains backups for 5 days and runs them between 7:00 and 9:00 UTC. Without additional configuration, the cluster uses the default VPC and stores the master password in Pulumi state.

Create an Aurora PostgreSQL cluster

Teams running PostgreSQL workloads can use Aurora PostgreSQL for managed replication and automatic failover.

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

const postgresql = new aws.rds.Cluster("postgresql", {
    clusterIdentifier: "aurora-cluster-demo",
    engine: aws.rds.EngineType.AuroraPostgresql,
    availabilityZones: [
        "us-west-2a",
        "us-west-2b",
        "us-west-2c",
    ],
    databaseName: "mydb",
    masterUsername: "foo",
    masterPassword: "must_be_eight_characters",
    backupRetentionPeriod: 5,
    preferredBackupWindow: "07:00-09:00",
});
import pulumi
import pulumi_aws as aws

postgresql = aws.rds.Cluster("postgresql",
    cluster_identifier="aurora-cluster-demo",
    engine=aws.rds.EngineType.AURORA_POSTGRESQL,
    availability_zones=[
        "us-west-2a",
        "us-west-2b",
        "us-west-2c",
    ],
    database_name="mydb",
    master_username="foo",
    master_password="must_be_eight_characters",
    backup_retention_period=5,
    preferred_backup_window="07:00-09:00")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := rds.NewCluster(ctx, "postgresql", &rds.ClusterArgs{
			ClusterIdentifier: pulumi.String("aurora-cluster-demo"),
			Engine:            pulumi.String(rds.EngineTypeAuroraPostgresql),
			AvailabilityZones: pulumi.StringArray{
				pulumi.String("us-west-2a"),
				pulumi.String("us-west-2b"),
				pulumi.String("us-west-2c"),
			},
			DatabaseName:          pulumi.String("mydb"),
			MasterUsername:        pulumi.String("foo"),
			MasterPassword:        pulumi.String("must_be_eight_characters"),
			BackupRetentionPeriod: pulumi.Int(5),
			PreferredBackupWindow: pulumi.String("07:00-09:00"),
		})
		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 postgresql = new Aws.Rds.Cluster("postgresql", new()
    {
        ClusterIdentifier = "aurora-cluster-demo",
        Engine = Aws.Rds.EngineType.AuroraPostgresql,
        AvailabilityZones = new[]
        {
            "us-west-2a",
            "us-west-2b",
            "us-west-2c",
        },
        DatabaseName = "mydb",
        MasterUsername = "foo",
        MasterPassword = "must_be_eight_characters",
        BackupRetentionPeriod = 5,
        PreferredBackupWindow = "07:00-09:00",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.rds.Cluster;
import com.pulumi.aws.rds.ClusterArgs;
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 postgresql = new Cluster("postgresql", ClusterArgs.builder()
            .clusterIdentifier("aurora-cluster-demo")
            .engine("aurora-postgresql")
            .availabilityZones(            
                "us-west-2a",
                "us-west-2b",
                "us-west-2c")
            .databaseName("mydb")
            .masterUsername("foo")
            .masterPassword("must_be_eight_characters")
            .backupRetentionPeriod(5)
            .preferredBackupWindow("07:00-09:00")
            .build());

    }
}
resources:
  postgresql:
    type: aws:rds:Cluster
    properties:
      clusterIdentifier: aurora-cluster-demo
      engine: aurora-postgresql
      availabilityZones:
        - us-west-2a
        - us-west-2b
        - us-west-2c
      databaseName: mydb
      masterUsername: foo
      masterPassword: must_be_eight_characters
      backupRetentionPeriod: 5
      preferredBackupWindow: 07:00-09:00

Setting engine to AuroraPostgresql switches from MySQL to PostgreSQL compatibility. The cluster structure remains the same: availability zones, backup configuration, and credentials. Aurora handles replication and failover automatically across the specified zones.

Create a Multi-AZ RDS cluster with provisioned IOPS

Multi-AZ RDS clusters provide high availability for MySQL and PostgreSQL with automatic failover. Unlike Aurora, these clusters require explicit storage and compute configuration.

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

const example = new aws.rds.Cluster("example", {
    clusterIdentifier: "example",
    availabilityZones: [
        "us-west-2a",
        "us-west-2b",
        "us-west-2c",
    ],
    engine: aws.rds.EngineType.Mysql,
    dbClusterInstanceClass: "db.r6gd.xlarge",
    storageType: "io1",
    allocatedStorage: 100,
    iops: 1000,
    masterUsername: "test",
    masterPassword: "mustbeeightcharaters",
});
import pulumi
import pulumi_aws as aws

example = aws.rds.Cluster("example",
    cluster_identifier="example",
    availability_zones=[
        "us-west-2a",
        "us-west-2b",
        "us-west-2c",
    ],
    engine=aws.rds.EngineType.MYSQL,
    db_cluster_instance_class="db.r6gd.xlarge",
    storage_type="io1",
    allocated_storage=100,
    iops=1000,
    master_username="test",
    master_password="mustbeeightcharaters")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := rds.NewCluster(ctx, "example", &rds.ClusterArgs{
			ClusterIdentifier: pulumi.String("example"),
			AvailabilityZones: pulumi.StringArray{
				pulumi.String("us-west-2a"),
				pulumi.String("us-west-2b"),
				pulumi.String("us-west-2c"),
			},
			Engine:                 pulumi.String(rds.EngineTypeMysql),
			DbClusterInstanceClass: pulumi.String("db.r6gd.xlarge"),
			StorageType:            pulumi.String("io1"),
			AllocatedStorage:       pulumi.Int(100),
			Iops:                   pulumi.Int(1000),
			MasterUsername:         pulumi.String("test"),
			MasterPassword:         pulumi.String("mustbeeightcharaters"),
		})
		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.Rds.Cluster("example", new()
    {
        ClusterIdentifier = "example",
        AvailabilityZones = new[]
        {
            "us-west-2a",
            "us-west-2b",
            "us-west-2c",
        },
        Engine = Aws.Rds.EngineType.Mysql,
        DbClusterInstanceClass = "db.r6gd.xlarge",
        StorageType = "io1",
        AllocatedStorage = 100,
        Iops = 1000,
        MasterUsername = "test",
        MasterPassword = "mustbeeightcharaters",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.rds.Cluster;
import com.pulumi.aws.rds.ClusterArgs;
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 Cluster("example", ClusterArgs.builder()
            .clusterIdentifier("example")
            .availabilityZones(            
                "us-west-2a",
                "us-west-2b",
                "us-west-2c")
            .engine("mysql")
            .dbClusterInstanceClass("db.r6gd.xlarge")
            .storageType("io1")
            .allocatedStorage(100)
            .iops(1000)
            .masterUsername("test")
            .masterPassword("mustbeeightcharaters")
            .build());

    }
}
resources:
  example:
    type: aws:rds:Cluster
    properties:
      clusterIdentifier: example
      availabilityZones:
        - us-west-2a
        - us-west-2b
        - us-west-2c
      engine: mysql
      dbClusterInstanceClass: db.r6gd.xlarge
      storageType: io1
      allocatedStorage: 100
      iops: 1000
      masterUsername: test
      masterPassword: mustbeeightcharaters

The dbClusterInstanceClass property defines the compute capacity for each instance in the cluster. The storageType, allocatedStorage, and iops properties configure provisioned IOPS storage: this cluster allocates 100 GiB with 1,000 IOPS. Multi-AZ clusters use standard MySQL or PostgreSQL engines, not Aurora-specific engines.

Configure Serverless v2 with auto-scaling

Serverless v2 clusters automatically scale compute capacity based on workload demand, eliminating the need to provision fixed instance sizes.

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

const example = new aws.rds.Cluster("example", {
    clusterIdentifier: "example",
    engine: aws.rds.EngineType.AuroraPostgresql,
    engineMode: aws.rds.EngineMode.Provisioned,
    engineVersion: "13.6",
    databaseName: "test",
    masterUsername: "test",
    masterPassword: "must_be_eight_characters",
    storageEncrypted: true,
    serverlessv2ScalingConfiguration: {
        maxCapacity: 1,
        minCapacity: 0,
        secondsUntilAutoPause: 3600,
    },
});
const exampleClusterInstance = new aws.rds.ClusterInstance("example", {
    clusterIdentifier: example.id,
    instanceClass: "db.serverless",
    engine: example.engine.apply((x) => aws.rds.EngineType[x]),
    engineVersion: example.engineVersion,
});
import pulumi
import pulumi_aws as aws

example = aws.rds.Cluster("example",
    cluster_identifier="example",
    engine=aws.rds.EngineType.AURORA_POSTGRESQL,
    engine_mode=aws.rds.EngineMode.PROVISIONED,
    engine_version="13.6",
    database_name="test",
    master_username="test",
    master_password="must_be_eight_characters",
    storage_encrypted=True,
    serverlessv2_scaling_configuration={
        "max_capacity": 1,
        "min_capacity": 0,
        "seconds_until_auto_pause": 3600,
    })
example_cluster_instance = aws.rds.ClusterInstance("example",
    cluster_identifier=example.id,
    instance_class="db.serverless",
    engine=example.engine,
    engine_version=example.engine_version)
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := rds.NewCluster(ctx, "example", &rds.ClusterArgs{
			ClusterIdentifier: pulumi.String("example"),
			Engine:            pulumi.String(rds.EngineTypeAuroraPostgresql),
			EngineMode:        pulumi.String(rds.EngineModeProvisioned),
			EngineVersion:     pulumi.String("13.6"),
			DatabaseName:      pulumi.String("test"),
			MasterUsername:    pulumi.String("test"),
			MasterPassword:    pulumi.String("must_be_eight_characters"),
			StorageEncrypted:  pulumi.Bool(true),
			Serverlessv2ScalingConfiguration: &rds.ClusterServerlessv2ScalingConfigurationArgs{
				MaxCapacity:           pulumi.Float64(1),
				MinCapacity:           pulumi.Float64(0),
				SecondsUntilAutoPause: pulumi.Int(3600),
			},
		})
		if err != nil {
			return err
		}
		_, err = rds.NewClusterInstance(ctx, "example", &rds.ClusterInstanceArgs{
			ClusterIdentifier: example.ID(),
			InstanceClass:     pulumi.String("db.serverless"),
			Engine:            example.Engine.ApplyT(func(x *string) rds.EngineType { return rds.EngineType(*x) }).(rds.EngineTypeOutput),
			EngineVersion:     example.EngineVersion,
		})
		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.Rds.Cluster("example", new()
    {
        ClusterIdentifier = "example",
        Engine = Aws.Rds.EngineType.AuroraPostgresql,
        EngineMode = Aws.Rds.EngineMode.Provisioned,
        EngineVersion = "13.6",
        DatabaseName = "test",
        MasterUsername = "test",
        MasterPassword = "must_be_eight_characters",
        StorageEncrypted = true,
        Serverlessv2ScalingConfiguration = new Aws.Rds.Inputs.ClusterServerlessv2ScalingConfigurationArgs
        {
            MaxCapacity = 1,
            MinCapacity = 0,
            SecondsUntilAutoPause = 3600,
        },
    });

    var exampleClusterInstance = new Aws.Rds.ClusterInstance("example", new()
    {
        ClusterIdentifier = example.Id,
        InstanceClass = "db.serverless",
        Engine = example.Engine.Apply(System.Enum.Parse<Aws.Rds.EngineType>),
        EngineVersion = example.EngineVersion,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.rds.Cluster;
import com.pulumi.aws.rds.ClusterArgs;
import com.pulumi.aws.rds.inputs.ClusterServerlessv2ScalingConfigurationArgs;
import com.pulumi.aws.rds.ClusterInstance;
import com.pulumi.aws.rds.ClusterInstanceArgs;
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 Cluster("example", ClusterArgs.builder()
            .clusterIdentifier("example")
            .engine("aurora-postgresql")
            .engineMode("provisioned")
            .engineVersion("13.6")
            .databaseName("test")
            .masterUsername("test")
            .masterPassword("must_be_eight_characters")
            .storageEncrypted(true)
            .serverlessv2ScalingConfiguration(ClusterServerlessv2ScalingConfigurationArgs.builder()
                .maxCapacity(1.0)
                .minCapacity(0.0)
                .secondsUntilAutoPause(3600)
                .build())
            .build());

        var exampleClusterInstance = new ClusterInstance("exampleClusterInstance", ClusterInstanceArgs.builder()
            .clusterIdentifier(example.id())
            .instanceClass("db.serverless")
            .engine(example.engine())
            .engineVersion(example.engineVersion())
            .build());

    }
}
resources:
  example:
    type: aws:rds:Cluster
    properties:
      clusterIdentifier: example
      engine: aurora-postgresql
      engineMode: provisioned
      engineVersion: '13.6'
      databaseName: test
      masterUsername: test
      masterPassword: must_be_eight_characters
      storageEncrypted: true
      serverlessv2ScalingConfiguration:
        maxCapacity: 1
        minCapacity: 0
        secondsUntilAutoPause: 3600
  exampleClusterInstance:
    type: aws:rds:ClusterInstance
    name: example
    properties:
      clusterIdentifier: ${example.id}
      instanceClass: db.serverless
      engine: ${example.engine}
      engineVersion: ${example.engineVersion}

The engineMode must be set to provisioned (not serverless) for Serverless v2. The serverlessv2ScalingConfiguration block defines capacity limits: minCapacity and maxCapacity control the range of Aurora Capacity Units (ACUs) the cluster can scale between. Unlike the basic examples, Serverless v2 requires a separate ClusterInstance resource with instanceClass set to db.serverless. The cluster scales automatically within the configured range based on CPU and connection metrics.

Manage master password with Secrets Manager

Instead of storing passwords in Pulumi state, RDS can generate and rotate master passwords automatically using AWS Secrets Manager.

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

const test = new aws.rds.Cluster("test", {
    clusterIdentifier: "example",
    databaseName: "test",
    manageMasterUserPassword: true,
    masterUsername: "test",
});
import pulumi
import pulumi_aws as aws

test = aws.rds.Cluster("test",
    cluster_identifier="example",
    database_name="test",
    manage_master_user_password=True,
    master_username="test")
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := rds.NewCluster(ctx, "test", &rds.ClusterArgs{
			ClusterIdentifier:        pulumi.String("example"),
			DatabaseName:             pulumi.String("test"),
			ManageMasterUserPassword: pulumi.Bool(true),
			MasterUsername:           pulumi.String("test"),
		})
		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 test = new Aws.Rds.Cluster("test", new()
    {
        ClusterIdentifier = "example",
        DatabaseName = "test",
        ManageMasterUserPassword = true,
        MasterUsername = "test",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.rds.Cluster;
import com.pulumi.aws.rds.ClusterArgs;
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 test = new Cluster("test", ClusterArgs.builder()
            .clusterIdentifier("example")
            .databaseName("test")
            .manageMasterUserPassword(true)
            .masterUsername("test")
            .build());

    }
}
resources:
  test:
    type: aws:rds:Cluster
    properties:
      clusterIdentifier: example
      databaseName: test
      manageMasterUserPassword: true
      masterUsername: test

Setting manageMasterUserPassword to true delegates password creation and rotation to Secrets Manager. You omit the masterPassword property entirely. RDS generates a strong password, stores it in Secrets Manager, and rotates it automatically. This removes credentials from your Pulumi state file and enables automatic rotation policies.

Restore a global cluster from snapshot

Global clusters span multiple AWS regions for disaster recovery and low-latency reads. When building from a snapshot, you must restore the cluster first, then attach it to the global cluster.

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

const example = aws.rds.getClusterSnapshot({
    dbClusterIdentifier: "example-original-cluster",
    mostRecent: true,
});
const exampleCluster = new aws.rds.Cluster("example", {
    engine: aws.rds.EngineType.Aurora,
    engineVersion: "5.6.mysql_aurora.1.22.4",
    clusterIdentifier: "example",
    snapshotIdentifier: example.then(example => example.id),
});
const exampleGlobalCluster = new aws.rds.GlobalCluster("example", {
    globalClusterIdentifier: "example",
    sourceDbClusterIdentifier: exampleCluster.arn,
    forceDestroy: true,
});
import pulumi
import pulumi_aws as aws

example = aws.rds.get_cluster_snapshot(db_cluster_identifier="example-original-cluster",
    most_recent=True)
example_cluster = aws.rds.Cluster("example",
    engine=aws.rds.EngineType.AURORA,
    engine_version="5.6.mysql_aurora.1.22.4",
    cluster_identifier="example",
    snapshot_identifier=example.id)
example_global_cluster = aws.rds.GlobalCluster("example",
    global_cluster_identifier="example",
    source_db_cluster_identifier=example_cluster.arn,
    force_destroy=True)
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := rds.LookupClusterSnapshot(ctx, &rds.LookupClusterSnapshotArgs{
			DbClusterIdentifier: pulumi.StringRef("example-original-cluster"),
			MostRecent:          pulumi.BoolRef(true),
		}, nil)
		if err != nil {
			return err
		}
		exampleCluster, err := rds.NewCluster(ctx, "example", &rds.ClusterArgs{
			Engine:             pulumi.String(rds.EngineTypeAurora),
			EngineVersion:      pulumi.String("5.6.mysql_aurora.1.22.4"),
			ClusterIdentifier:  pulumi.String("example"),
			SnapshotIdentifier: pulumi.String(example.Id),
		})
		if err != nil {
			return err
		}
		_, err = rds.NewGlobalCluster(ctx, "example", &rds.GlobalClusterArgs{
			GlobalClusterIdentifier:   pulumi.String("example"),
			SourceDbClusterIdentifier: exampleCluster.Arn,
			ForceDestroy:              pulumi.Bool(true),
		})
		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 = Aws.Rds.GetClusterSnapshot.Invoke(new()
    {
        DbClusterIdentifier = "example-original-cluster",
        MostRecent = true,
    });

    var exampleCluster = new Aws.Rds.Cluster("example", new()
    {
        Engine = Aws.Rds.EngineType.Aurora,
        EngineVersion = "5.6.mysql_aurora.1.22.4",
        ClusterIdentifier = "example",
        SnapshotIdentifier = example.Apply(getClusterSnapshotResult => getClusterSnapshotResult.Id),
    });

    var exampleGlobalCluster = new Aws.Rds.GlobalCluster("example", new()
    {
        GlobalClusterIdentifier = "example",
        SourceDbClusterIdentifier = exampleCluster.Arn,
        ForceDestroy = true,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.rds.RdsFunctions;
import com.pulumi.aws.rds.inputs.GetClusterSnapshotArgs;
import com.pulumi.aws.rds.Cluster;
import com.pulumi.aws.rds.ClusterArgs;
import com.pulumi.aws.rds.GlobalCluster;
import com.pulumi.aws.rds.GlobalClusterArgs;
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 example = RdsFunctions.getClusterSnapshot(GetClusterSnapshotArgs.builder()
            .dbClusterIdentifier("example-original-cluster")
            .mostRecent(true)
            .build());

        var exampleCluster = new Cluster("exampleCluster", ClusterArgs.builder()
            .engine("aurora")
            .engineVersion("5.6.mysql_aurora.1.22.4")
            .clusterIdentifier("example")
            .snapshotIdentifier(example.id())
            .build());

        var exampleGlobalCluster = new GlobalCluster("exampleGlobalCluster", GlobalClusterArgs.builder()
            .globalClusterIdentifier("example")
            .sourceDbClusterIdentifier(exampleCluster.arn())
            .forceDestroy(true)
            .build());

    }
}
resources:
  exampleCluster:
    type: aws:rds:Cluster
    name: example
    properties:
      engine: aurora
      engineVersion: 5.6.mysql_aurora.1.22.4
      clusterIdentifier: example
      snapshotIdentifier: ${example.id}
  exampleGlobalCluster:
    type: aws:rds:GlobalCluster
    name: example
    properties:
      globalClusterIdentifier: example
      sourceDbClusterIdentifier: ${exampleCluster.arn}
      forceDestroy: true
variables:
  example:
    fn::invoke:
      function: aws:rds:getClusterSnapshot
      arguments:
        dbClusterIdentifier: example-original-cluster
        mostRecent: true

The getClusterSnapshot function retrieves the most recent snapshot from an existing cluster. The snapshotIdentifier property restores the cluster from that snapshot. After the cluster is created, the GlobalCluster resource references it via sourceDbClusterIdentifier to establish the global cluster. This two-step process is required: you cannot restore from snapshot and join a global cluster in a single operation.

Beyond these examples

These snippets focus on specific cluster-level features: Aurora and Multi-AZ cluster types, Serverless v2 auto-scaling, Secrets Manager password management, and global clusters and snapshot restoration. They’re intentionally minimal rather than full database deployments.

The examples may reference pre-existing infrastructure such as VPC, subnets, and security groups (most examples use defaults), DB snapshots (for restoration examples), and IAM permissions for Secrets Manager. They focus on configuring the cluster rather than provisioning the surrounding network infrastructure.

To keep things focused, common cluster patterns are omitted, including:

  • Storage encryption (storageEncrypted, kmsKeyId)
  • Network configuration (vpcSecurityGroupIds, dbSubnetGroupName)
  • Performance Insights and Enhanced Monitoring
  • Cluster parameter groups and instance parameter groups
  • IAM database authentication
  • CloudWatch log exports

These omissions are intentional: the goal is to illustrate how each cluster feature is wired, not provide drop-in database modules. See the RDS Cluster resource reference for all available configuration options.

Let's create AWS RDS Aurora Clusters

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Cluster Types & Configuration
What's the difference between Aurora and Multi-AZ RDS clusters?
Aurora clusters use engines aurora-mysql or aurora-postgresql, while Multi-AZ RDS clusters use mysql or postgres. Multi-AZ clusters require additional properties: storage_type, allocated_storage, iops, and db_cluster_instance_class.
How do I create a Serverless v2 cluster?
Set engine_mode to provisioned (not serverless), configure serverlessv2_scaling_configuration, and add an aws.rds.ClusterInstance with instance_class set to db.serverless. Note that storage_encrypted defaults to false for provisioned mode, unlike Serverless v1.
What's the difference between Serverless v1 and v2?
Serverless v1 uses engine_mode set to serverless with storage_encrypted defaulting to true. Serverless v2 uses engine_mode set to provisioned with storage_encrypted defaulting to false, and requires explicit serverlessv2_scaling_configuration and cluster instances.
Which engines support backtracking?
Only aurora and aurora-mysql engines support backtracking. Set backtrack_window between 0 and 259200 seconds (72 hours). Default is 0 (disabled).
Security & Credentials
Why is my master password stored in plain-text in the state file?
All arguments including master_username and master_password are stored in plain-text in the raw state. To avoid this, use manage_master_user_password set to true, which stores the password in AWS Secrets Manager instead.
Can I use both manage_master_user_password and master_password?
No, these are mutually exclusive. Set manage_master_user_password to true to use Secrets Manager, or provide master_password directly. When switching to managed passwords, you must remove the master_password attribute.
Why am I getting conflicts with iam_roles?
Pulumi provides both a standalone RDS Cluster Role Association resource and the iam_roles attribute on the cluster. Using both causes conflicts and overwrites. Choose one approach and stick with it.
What are the encryption defaults for RDS clusters?
For provisioned engine_mode, storage_encrypted defaults to false. For serverless engine_mode, it defaults to true. When restoring from an unencrypted snapshot, you must provide kms_key_id to encrypt the restored cluster.
Maintenance & Updates
What causes downtime during cluster updates?
Setting apply_immediately to true causes brief downtime as the server reboots. Updating engine_version also results in an outage. By default, changes are applied during the next maintenance window to minimize disruption.
When are cluster modifications applied?
By default (apply_immediately is false), modifications are applied during the next maintenance window. Set apply_immediately to true to apply changes immediately, but this may cause downtime.
Snapshots & Backups
Can I restore from a snapshot and join a global cluster at the same time?
No, snapshot_identifier conflicts with global_cluster_identifier. You must restore from the snapshot first, then join the global cluster in a separate operation.
How do I prevent a final snapshot when deleting a cluster?
Set skip_final_snapshot to true. By default, it’s false, which creates a final snapshot using the name from final_snapshot_identifier before deletion.
Networking & Availability
Why does Pulumi show a diff for availability_zones after creation?
RDS automatically assigns 3 AZs if fewer than 3 are configured, causing Pulumi to detect a difference requiring resource recreation. Specify exactly 3 AZs or use lifecycle with ignore_changes for availability_zones.
Why must db_subnet_group_name match across all cluster instances?
The db_subnet_group_name on the cluster must match the value specified on every aws.rds.ClusterInstance in the cluster. Mismatches cause configuration errors.
Common Errors & Gotchas
What cluster properties are immutable after creation?
These properties cannot be changed without recreating the cluster: availability_zones, cluster_identifier, cluster_identifier_prefix, database_name, db_subnet_group_name, db_system_id, engine, engine_mode, kms_key_id, master_username, restore_to_point_in_time, snapshot_identifier, source_region, storage_encrypted, and cluster_scalability_type.
Can I change master_username during a snapshot restore?
No, master_username does not support in-place updates and cannot be changed during a restore from snapshot.
What's required to use ca_certificate_identifier?
The ca_certificate_identifier property is only supported for Multi-AZ DB clusters (engines mysql or postgres), not Aurora clusters.

Using a different cloud?

Explore database guides for other cloud providers: