Create and Configure DynamoDB Tables

The aws:dynamodb/table:Table resource, part of the Pulumi AWS provider, defines a DynamoDB table’s structure: its primary keys, attributes, indexes, and optional multi-region replication. This guide focuses on three capabilities: primary key and index configuration, multi-region replication, and strong consistency modes.

DynamoDB tables require attribute definitions for keys and indexes. Global tables require AWS provider configuration for each replica region. The examples are intentionally small. Combine them with your own capacity planning, encryption, and backup configuration.

Define a table with hash key and global secondary index

Most deployments start with a single-region table that defines a primary key structure and optional secondary indexes for alternate query patterns.

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

const basic_dynamodb_table = new aws.dynamodb.Table("basic-dynamodb-table", {
    name: "GameScores",
    billingMode: "PROVISIONED",
    readCapacity: 20,
    writeCapacity: 20,
    hashKey: "UserId",
    rangeKey: "GameTitle",
    attributes: [
        {
            name: "UserId",
            type: "S",
        },
        {
            name: "GameTitle",
            type: "S",
        },
        {
            name: "TopScore",
            type: "N",
        },
    ],
    ttl: {
        attributeName: "TimeToExist",
        enabled: true,
    },
    globalSecondaryIndexes: [{
        name: "GameTitleIndex",
        hashKey: "GameTitle",
        rangeKey: "TopScore",
        writeCapacity: 10,
        readCapacity: 10,
        projectionType: "INCLUDE",
        nonKeyAttributes: ["UserId"],
    }],
    tags: {
        Name: "dynamodb-table-1",
        Environment: "production",
    },
});
import pulumi
import pulumi_aws as aws

basic_dynamodb_table = aws.dynamodb.Table("basic-dynamodb-table",
    name="GameScores",
    billing_mode="PROVISIONED",
    read_capacity=20,
    write_capacity=20,
    hash_key="UserId",
    range_key="GameTitle",
    attributes=[
        {
            "name": "UserId",
            "type": "S",
        },
        {
            "name": "GameTitle",
            "type": "S",
        },
        {
            "name": "TopScore",
            "type": "N",
        },
    ],
    ttl={
        "attribute_name": "TimeToExist",
        "enabled": True,
    },
    global_secondary_indexes=[{
        "name": "GameTitleIndex",
        "hash_key": "GameTitle",
        "range_key": "TopScore",
        "write_capacity": 10,
        "read_capacity": 10,
        "projection_type": "INCLUDE",
        "non_key_attributes": ["UserId"],
    }],
    tags={
        "Name": "dynamodb-table-1",
        "Environment": "production",
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := dynamodb.NewTable(ctx, "basic-dynamodb-table", &dynamodb.TableArgs{
			Name:          pulumi.String("GameScores"),
			BillingMode:   pulumi.String("PROVISIONED"),
			ReadCapacity:  pulumi.Int(20),
			WriteCapacity: pulumi.Int(20),
			HashKey:       pulumi.String("UserId"),
			RangeKey:      pulumi.String("GameTitle"),
			Attributes: dynamodb.TableAttributeArray{
				&dynamodb.TableAttributeArgs{
					Name: pulumi.String("UserId"),
					Type: pulumi.String("S"),
				},
				&dynamodb.TableAttributeArgs{
					Name: pulumi.String("GameTitle"),
					Type: pulumi.String("S"),
				},
				&dynamodb.TableAttributeArgs{
					Name: pulumi.String("TopScore"),
					Type: pulumi.String("N"),
				},
			},
			Ttl: &dynamodb.TableTtlArgs{
				AttributeName: pulumi.String("TimeToExist"),
				Enabled:       pulumi.Bool(true),
			},
			GlobalSecondaryIndexes: dynamodb.TableGlobalSecondaryIndexArray{
				&dynamodb.TableGlobalSecondaryIndexArgs{
					Name:           pulumi.String("GameTitleIndex"),
					HashKey:        pulumi.String("GameTitle"),
					RangeKey:       pulumi.String("TopScore"),
					WriteCapacity:  pulumi.Int(10),
					ReadCapacity:   pulumi.Int(10),
					ProjectionType: pulumi.String("INCLUDE"),
					NonKeyAttributes: pulumi.StringArray{
						pulumi.String("UserId"),
					},
				},
			},
			Tags: pulumi.StringMap{
				"Name":        pulumi.String("dynamodb-table-1"),
				"Environment": pulumi.String("production"),
			},
		})
		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 basic_dynamodb_table = new Aws.DynamoDB.Table("basic-dynamodb-table", new()
    {
        Name = "GameScores",
        BillingMode = "PROVISIONED",
        ReadCapacity = 20,
        WriteCapacity = 20,
        HashKey = "UserId",
        RangeKey = "GameTitle",
        Attributes = new[]
        {
            new Aws.DynamoDB.Inputs.TableAttributeArgs
            {
                Name = "UserId",
                Type = "S",
            },
            new Aws.DynamoDB.Inputs.TableAttributeArgs
            {
                Name = "GameTitle",
                Type = "S",
            },
            new Aws.DynamoDB.Inputs.TableAttributeArgs
            {
                Name = "TopScore",
                Type = "N",
            },
        },
        Ttl = new Aws.DynamoDB.Inputs.TableTtlArgs
        {
            AttributeName = "TimeToExist",
            Enabled = true,
        },
        GlobalSecondaryIndexes = new[]
        {
            new Aws.DynamoDB.Inputs.TableGlobalSecondaryIndexArgs
            {
                Name = "GameTitleIndex",
                HashKey = "GameTitle",
                RangeKey = "TopScore",
                WriteCapacity = 10,
                ReadCapacity = 10,
                ProjectionType = "INCLUDE",
                NonKeyAttributes = new[]
                {
                    "UserId",
                },
            },
        },
        Tags = 
        {
            { "Name", "dynamodb-table-1" },
            { "Environment", "production" },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.dynamodb.Table;
import com.pulumi.aws.dynamodb.TableArgs;
import com.pulumi.aws.dynamodb.inputs.TableAttributeArgs;
import com.pulumi.aws.dynamodb.inputs.TableTtlArgs;
import com.pulumi.aws.dynamodb.inputs.TableGlobalSecondaryIndexArgs;
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 basic_dynamodb_table = new Table("basic-dynamodb-table", TableArgs.builder()
            .name("GameScores")
            .billingMode("PROVISIONED")
            .readCapacity(20)
            .writeCapacity(20)
            .hashKey("UserId")
            .rangeKey("GameTitle")
            .attributes(            
                TableAttributeArgs.builder()
                    .name("UserId")
                    .type("S")
                    .build(),
                TableAttributeArgs.builder()
                    .name("GameTitle")
                    .type("S")
                    .build(),
                TableAttributeArgs.builder()
                    .name("TopScore")
                    .type("N")
                    .build())
            .ttl(TableTtlArgs.builder()
                .attributeName("TimeToExist")
                .enabled(true)
                .build())
            .globalSecondaryIndexes(TableGlobalSecondaryIndexArgs.builder()
                .name("GameTitleIndex")
                .hashKey("GameTitle")
                .rangeKey("TopScore")
                .writeCapacity(10)
                .readCapacity(10)
                .projectionType("INCLUDE")
                .nonKeyAttributes("UserId")
                .build())
            .tags(Map.ofEntries(
                Map.entry("Name", "dynamodb-table-1"),
                Map.entry("Environment", "production")
            ))
            .build());

    }
}
resources:
  basic-dynamodb-table:
    type: aws:dynamodb:Table
    properties:
      name: GameScores
      billingMode: PROVISIONED
      readCapacity: 20
      writeCapacity: 20
      hashKey: UserId
      rangeKey: GameTitle
      attributes:
        - name: UserId
          type: S
        - name: GameTitle
          type: S
        - name: TopScore
          type: N
      ttl:
        attributeName: TimeToExist
        enabled: true
      globalSecondaryIndexes:
        - name: GameTitleIndex
          hashKey: GameTitle
          rangeKey: TopScore
          writeCapacity: 10
          readCapacity: 10
          projectionType: INCLUDE
          nonKeyAttributes:
            - UserId
      tags:
        Name: dynamodb-table-1
        Environment: production

The hashKey and rangeKey properties define the primary key structure (partition key and sort key). Every attribute used in keys or indexes must appear in the attributes array with its name and type. The globalSecondaryIndexes block creates alternate query patterns; here, GameTitleIndex lets you query by game title and top score instead of user ID. The billingMode controls whether you pay per request or provision fixed capacity.

Replicate tables across multiple regions

Applications serving global users often replicate tables to multiple regions for low-latency access and disaster recovery.

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

const example = new aws.dynamodb.Table("example", {
    name: "example",
    hashKey: "TestTableHashKey",
    billingMode: "PAY_PER_REQUEST",
    streamEnabled: true,
    streamViewType: "NEW_AND_OLD_IMAGES",
    attributes: [{
        name: "TestTableHashKey",
        type: "S",
    }],
    replicas: [
        {
            regionName: "us-east-2",
        },
        {
            regionName: "us-west-2",
        },
    ],
});
import pulumi
import pulumi_aws as aws

example = aws.dynamodb.Table("example",
    name="example",
    hash_key="TestTableHashKey",
    billing_mode="PAY_PER_REQUEST",
    stream_enabled=True,
    stream_view_type="NEW_AND_OLD_IMAGES",
    attributes=[{
        "name": "TestTableHashKey",
        "type": "S",
    }],
    replicas=[
        {
            "region_name": "us-east-2",
        },
        {
            "region_name": "us-west-2",
        },
    ])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := dynamodb.NewTable(ctx, "example", &dynamodb.TableArgs{
			Name:           pulumi.String("example"),
			HashKey:        pulumi.String("TestTableHashKey"),
			BillingMode:    pulumi.String("PAY_PER_REQUEST"),
			StreamEnabled:  pulumi.Bool(true),
			StreamViewType: pulumi.String("NEW_AND_OLD_IMAGES"),
			Attributes: dynamodb.TableAttributeArray{
				&dynamodb.TableAttributeArgs{
					Name: pulumi.String("TestTableHashKey"),
					Type: pulumi.String("S"),
				},
			},
			Replicas: dynamodb.TableReplicaTypeArray{
				&dynamodb.TableReplicaTypeArgs{
					RegionName: pulumi.String("us-east-2"),
				},
				&dynamodb.TableReplicaTypeArgs{
					RegionName: pulumi.String("us-west-2"),
				},
			},
		})
		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.DynamoDB.Table("example", new()
    {
        Name = "example",
        HashKey = "TestTableHashKey",
        BillingMode = "PAY_PER_REQUEST",
        StreamEnabled = true,
        StreamViewType = "NEW_AND_OLD_IMAGES",
        Attributes = new[]
        {
            new Aws.DynamoDB.Inputs.TableAttributeArgs
            {
                Name = "TestTableHashKey",
                Type = "S",
            },
        },
        Replicas = new[]
        {
            new Aws.DynamoDB.Inputs.TableReplicaArgs
            {
                RegionName = "us-east-2",
            },
            new Aws.DynamoDB.Inputs.TableReplicaArgs
            {
                RegionName = "us-west-2",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.dynamodb.Table;
import com.pulumi.aws.dynamodb.TableArgs;
import com.pulumi.aws.dynamodb.inputs.TableAttributeArgs;
import com.pulumi.aws.dynamodb.inputs.TableReplicaArgs;
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 Table("example", TableArgs.builder()
            .name("example")
            .hashKey("TestTableHashKey")
            .billingMode("PAY_PER_REQUEST")
            .streamEnabled(true)
            .streamViewType("NEW_AND_OLD_IMAGES")
            .attributes(TableAttributeArgs.builder()
                .name("TestTableHashKey")
                .type("S")
                .build())
            .replicas(            
                TableReplicaArgs.builder()
                    .regionName("us-east-2")
                    .build(),
                TableReplicaArgs.builder()
                    .regionName("us-west-2")
                    .build())
            .build());

    }
}
resources:
  example:
    type: aws:dynamodb:Table
    properties:
      name: example
      hashKey: TestTableHashKey
      billingMode: PAY_PER_REQUEST
      streamEnabled: true
      streamViewType: NEW_AND_OLD_IMAGES
      attributes:
        - name: TestTableHashKey
          type: S
      replicas:
        - regionName: us-east-2
        - regionName: us-west-2

The replicas array defines target regions for replication. DynamoDB automatically synchronizes writes across all replicas with eventual consistency. The streamEnabled and streamViewType properties are required for replication; they capture change events that DynamoDB uses to propagate updates. Each replica maintains its own copy of the data.

Enable strong consistency across three regions

Some applications require strongly consistent reads across regions, ensuring every read returns the most recent write regardless of which region handles the request.

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

const example = new aws.dynamodb.Table("example", {
    name: "example",
    hashKey: "TestTableHashKey",
    billingMode: "PAY_PER_REQUEST",
    streamEnabled: true,
    streamViewType: "NEW_AND_OLD_IMAGES",
    attributes: [{
        name: "TestTableHashKey",
        type: "S",
    }],
    replicas: [
        {
            regionName: "us-east-2",
            consistencyMode: "STRONG",
        },
        {
            regionName: "us-west-2",
            consistencyMode: "STRONG",
        },
    ],
});
import pulumi
import pulumi_aws as aws

example = aws.dynamodb.Table("example",
    name="example",
    hash_key="TestTableHashKey",
    billing_mode="PAY_PER_REQUEST",
    stream_enabled=True,
    stream_view_type="NEW_AND_OLD_IMAGES",
    attributes=[{
        "name": "TestTableHashKey",
        "type": "S",
    }],
    replicas=[
        {
            "region_name": "us-east-2",
            "consistency_mode": "STRONG",
        },
        {
            "region_name": "us-west-2",
            "consistency_mode": "STRONG",
        },
    ])
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := dynamodb.NewTable(ctx, "example", &dynamodb.TableArgs{
			Name:           pulumi.String("example"),
			HashKey:        pulumi.String("TestTableHashKey"),
			BillingMode:    pulumi.String("PAY_PER_REQUEST"),
			StreamEnabled:  pulumi.Bool(true),
			StreamViewType: pulumi.String("NEW_AND_OLD_IMAGES"),
			Attributes: dynamodb.TableAttributeArray{
				&dynamodb.TableAttributeArgs{
					Name: pulumi.String("TestTableHashKey"),
					Type: pulumi.String("S"),
				},
			},
			Replicas: dynamodb.TableReplicaTypeArray{
				&dynamodb.TableReplicaTypeArgs{
					RegionName:      pulumi.String("us-east-2"),
					ConsistencyMode: pulumi.String("STRONG"),
				},
				&dynamodb.TableReplicaTypeArgs{
					RegionName:      pulumi.String("us-west-2"),
					ConsistencyMode: pulumi.String("STRONG"),
				},
			},
		})
		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.DynamoDB.Table("example", new()
    {
        Name = "example",
        HashKey = "TestTableHashKey",
        BillingMode = "PAY_PER_REQUEST",
        StreamEnabled = true,
        StreamViewType = "NEW_AND_OLD_IMAGES",
        Attributes = new[]
        {
            new Aws.DynamoDB.Inputs.TableAttributeArgs
            {
                Name = "TestTableHashKey",
                Type = "S",
            },
        },
        Replicas = new[]
        {
            new Aws.DynamoDB.Inputs.TableReplicaArgs
            {
                RegionName = "us-east-2",
                ConsistencyMode = "STRONG",
            },
            new Aws.DynamoDB.Inputs.TableReplicaArgs
            {
                RegionName = "us-west-2",
                ConsistencyMode = "STRONG",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.dynamodb.Table;
import com.pulumi.aws.dynamodb.TableArgs;
import com.pulumi.aws.dynamodb.inputs.TableAttributeArgs;
import com.pulumi.aws.dynamodb.inputs.TableReplicaArgs;
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 Table("example", TableArgs.builder()
            .name("example")
            .hashKey("TestTableHashKey")
            .billingMode("PAY_PER_REQUEST")
            .streamEnabled(true)
            .streamViewType("NEW_AND_OLD_IMAGES")
            .attributes(TableAttributeArgs.builder()
                .name("TestTableHashKey")
                .type("S")
                .build())
            .replicas(            
                TableReplicaArgs.builder()
                    .regionName("us-east-2")
                    .consistencyMode("STRONG")
                    .build(),
                TableReplicaArgs.builder()
                    .regionName("us-west-2")
                    .consistencyMode("STRONG")
                    .build())
            .build());

    }
}
resources:
  example:
    type: aws:dynamodb:Table
    properties:
      name: example
      hashKey: TestTableHashKey
      billingMode: PAY_PER_REQUEST
      streamEnabled: true
      streamViewType: NEW_AND_OLD_IMAGES
      attributes:
        - name: TestTableHashKey
          type: S
      replicas:
        - regionName: us-east-2
          consistencyMode: STRONG
        - regionName: us-west-2
          consistencyMode: STRONG

Setting consistencyMode to STRONG on each replica enables multi-region strong consistency. This guarantees that reads in any replica region return the latest data, at the cost of higher latency compared to eventual consistency. You need at least three replicas for this configuration.

Use a witness region for two-replica strong consistency

When you need strong consistency but want to minimize full replica costs, a witness region provides the consistency guarantees without supporting read or write operations.

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

const example = new aws.dynamodb.Table("example", {
    name: "example",
    hashKey: "TestTableHashKey",
    billingMode: "PAY_PER_REQUEST",
    streamEnabled: true,
    streamViewType: "NEW_AND_OLD_IMAGES",
    attributes: [{
        name: "TestTableHashKey",
        type: "S",
    }],
    replicas: [{
        regionName: "us-east-2",
        consistencyMode: "STRONG",
    }],
    globalTableWitness: {
        regionName: "us-west-2",
    },
});
import pulumi
import pulumi_aws as aws

example = aws.dynamodb.Table("example",
    name="example",
    hash_key="TestTableHashKey",
    billing_mode="PAY_PER_REQUEST",
    stream_enabled=True,
    stream_view_type="NEW_AND_OLD_IMAGES",
    attributes=[{
        "name": "TestTableHashKey",
        "type": "S",
    }],
    replicas=[{
        "region_name": "us-east-2",
        "consistency_mode": "STRONG",
    }],
    global_table_witness={
        "region_name": "us-west-2",
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := dynamodb.NewTable(ctx, "example", &dynamodb.TableArgs{
			Name:           pulumi.String("example"),
			HashKey:        pulumi.String("TestTableHashKey"),
			BillingMode:    pulumi.String("PAY_PER_REQUEST"),
			StreamEnabled:  pulumi.Bool(true),
			StreamViewType: pulumi.String("NEW_AND_OLD_IMAGES"),
			Attributes: dynamodb.TableAttributeArray{
				&dynamodb.TableAttributeArgs{
					Name: pulumi.String("TestTableHashKey"),
					Type: pulumi.String("S"),
				},
			},
			Replicas: dynamodb.TableReplicaTypeArray{
				&dynamodb.TableReplicaTypeArgs{
					RegionName:      pulumi.String("us-east-2"),
					ConsistencyMode: pulumi.String("STRONG"),
				},
			},
			GlobalTableWitness: &dynamodb.TableGlobalTableWitnessArgs{
				RegionName: pulumi.String("us-west-2"),
			},
		})
		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.DynamoDB.Table("example", new()
    {
        Name = "example",
        HashKey = "TestTableHashKey",
        BillingMode = "PAY_PER_REQUEST",
        StreamEnabled = true,
        StreamViewType = "NEW_AND_OLD_IMAGES",
        Attributes = new[]
        {
            new Aws.DynamoDB.Inputs.TableAttributeArgs
            {
                Name = "TestTableHashKey",
                Type = "S",
            },
        },
        Replicas = new[]
        {
            new Aws.DynamoDB.Inputs.TableReplicaArgs
            {
                RegionName = "us-east-2",
                ConsistencyMode = "STRONG",
            },
        },
        GlobalTableWitness = new Aws.DynamoDB.Inputs.TableGlobalTableWitnessArgs
        {
            RegionName = "us-west-2",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.dynamodb.Table;
import com.pulumi.aws.dynamodb.TableArgs;
import com.pulumi.aws.dynamodb.inputs.TableAttributeArgs;
import com.pulumi.aws.dynamodb.inputs.TableReplicaArgs;
import com.pulumi.aws.dynamodb.inputs.TableGlobalTableWitnessArgs;
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 Table("example", TableArgs.builder()
            .name("example")
            .hashKey("TestTableHashKey")
            .billingMode("PAY_PER_REQUEST")
            .streamEnabled(true)
            .streamViewType("NEW_AND_OLD_IMAGES")
            .attributes(TableAttributeArgs.builder()
                .name("TestTableHashKey")
                .type("S")
                .build())
            .replicas(TableReplicaArgs.builder()
                .regionName("us-east-2")
                .consistencyMode("STRONG")
                .build())
            .globalTableWitness(TableGlobalTableWitnessArgs.builder()
                .regionName("us-west-2")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:dynamodb:Table
    properties:
      name: example
      hashKey: TestTableHashKey
      billingMode: PAY_PER_REQUEST
      streamEnabled: true
      streamViewType: NEW_AND_OLD_IMAGES
      attributes:
        - name: TestTableHashKey
          type: S
      replicas:
        - regionName: us-east-2
          consistencyMode: STRONG
      globalTableWitness:
        regionName: us-west-2

The globalTableWitness property designates a region that participates in consistency protocols but doesn’t store a full copy of the data or handle requests. This reduces costs compared to three full replicas while maintaining strong consistency between the two active replicas. The witness region must differ from both replica regions.

Beyond these examples

These snippets focus on specific table-level features: single-region and multi-region table configuration, global secondary indexes, and strong consistency modes and witness regions. They’re intentionally minimal rather than full database deployments.

The examples may reference pre-existing infrastructure such as AWS provider configured for target regions. They focus on configuring the table rather than provisioning everything around it.

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

  • Point-in-time recovery (pointInTimeRecovery)
  • Server-side encryption with customer-managed keys (serverSideEncryption)
  • Time-to-live expiration (ttl)
  • Local secondary indexes (localSecondaryIndexes)
  • Capacity planning (readCapacity, writeCapacity, onDemandThroughput)
  • Table restoration from backups (restoreSourceName, restoreDateTime)

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

Let's create and Configure DynamoDB Tables

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Attributes & Schema Design
Why am I seeing an infinite loop when planning my DynamoDB table?
This happens when you define attributes that aren’t used as keys. Only define attributes in the attributes block if they’re used as the table’s hashKey or rangeKey, or as hash/range keys in GSIs or LSIs. Adding unused attributes causes infinite planning loops.
What attributes should I define in my table configuration?
Only define attributes that serve as keys: the table’s hashKey and rangeKey, plus any attributes used as hash or range keys in Global Secondary Indexes (GSIs) or Local Secondary Indexes (LSIs). Don’t define other attributes even if they exist in your data.
Capacity & Billing
What's the difference between PROVISIONED and PAY_PER_REQUEST billing modes?
PROVISIONED (default) requires you to specify readCapacity and writeCapacity values. PAY_PER_REQUEST automatically scales and doesn’t require capacity settings. Choose PAY_PER_REQUEST for unpredictable workloads or PROVISIONED for steady, predictable traffic.
How do I avoid conflicts when using autoscaling with my table?
Use ignoreChanges for readCapacity and writeCapacity when you have an autoscaling policy attached. This prevents Pulumi from trying to manage capacity values that autoscaling controls dynamically.
Global Tables & Replication
Can I use both replica blocks and aws.dynamodb.TableReplica resource?
No, you must choose one method. Don’t use inline replica configuration blocks together with the aws.dynamodb.TableReplica resource, as they conflict. Pick the approach that fits your infrastructure organization.
How do I set up Multi-Region Strong Consistency with a witness region?
Configure globalTableWitness with a region name alongside exactly one replica that has consistencyMode set to STRONG. Other combinations (multiple replicas, missing STRONG mode, or no replica) will fail to provision.
What's the difference between Global Tables V1 and V2?
This resource implements Global Tables V2 (version 2019.11.21) using replica configuration blocks. For Global Tables V1 (version 2017.11.29), use the separate aws.dynamodb.GlobalTable resource instead.
How do I manage tags on global table replicas?
You can set propagateTags to true on individual replicas to inherit tags from the main table, or use the aws.dynamodb.Tag resource to manage replica tags independently in each region.
Immutability & Lifecycle
What can't I change after creating my DynamoDB table?
Several properties are immutable: name, hashKey, rangeKey, and localSecondaryIndexes. LSIs can only be allocated at table creation. If you need to change these, you must recreate the table.
Why do I need ignoreChanges for replica when using aws.dynamodb.TableReplica?
When using the aws.dynamodb.TableReplica resource, add ignoreChanges for the replica property to prevent conflicts between the inline configuration and the separate resource managing the same replicas.
Encryption & Cross-Region
When do I need to specify serverSideEncryption?
You must supply serverSideEncryption configuration for cross-region restores. Otherwise, DynamoDB tables are automatically encrypted at rest with an AWS-owned Customer Master Key if you don’t specify this argument.

Using a different cloud?

Explore dynamodb guides for other cloud providers: