The aws:dynamodb/table:Table resource, part of the Pulumi AWS provider, defines a DynamoDB table: its key schema, capacity mode, indexes, and optional replication across regions. This guide focuses on three capabilities: primary key and index configuration, multi-region replication, and consistency modes for global tables.
A DynamoDB table often connects to other AWS services. Auto Scaling policies can manage capacity, and global tables require AWS provider configuration for additional regions. The examples are intentionally small and show feature-level configuration. Combine them with your own capacity planning and monitoring infrastructure.
Define a table with hash key, range key, and GSI
Most deployments start with a partition key for data distribution, a sort key for query flexibility, and global secondary indexes for alternate access 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 defines the partition key that distributes items across storage nodes. The rangeKey enables range queries within a partition. The attributes array must list all keys used in the table or indexes. The globalSecondaryIndexes array defines alternate query patterns, each with its own key structure and capacity settings. This example uses PROVISIONED billing, requiring explicit readCapacity and writeCapacity values.
Replicate tables across multiple AWS regions
Applications serving global users need low-latency access from multiple regions. DynamoDB replicates data automatically across configured replicas.
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 lists target regions for replication. Each replica receives a synchronized copy of table data. Setting streamEnabled to true and specifying streamViewType enables the change data capture that powers replication. Global tables require PAY_PER_REQUEST billing mode rather than provisioned capacity.
Enable strong consistency across global table replicas
Some workloads require strongly consistent reads across regions, guaranteeing the latest version regardless of which region serves 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
The consistencyMode property on each replica controls read consistency. Setting it to STRONG ensures reads always return the latest version, at the cost of higher latency compared to eventually consistent reads. This extends the basic global tables configuration with stronger guarantees but has specific AWS requirements and limitations.
Beyond These Examples
These snippets focus on specific table-level features: primary key structure (hash and range keys), global secondary indexes for alternate queries, and multi-region replication with eventual and strong consistency. They’re intentionally minimal rather than full database deployments.
The examples may reference pre-existing infrastructure such as AWS provider configured for the primary region, and additional AWS providers or region configuration for replicas. They focus on configuring the table rather than provisioning surrounding infrastructure.
To keep things focused, common table patterns are omitted, including:
- Time-to-live (TTL) for automatic item expiration
- Point-in-time recovery and backup configuration
- Server-side encryption with customer-managed keys
- Auto Scaling policies for capacity management
- Local secondary indexes (LSIs)
- Stream consumers and change data capture
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.
Frequently Asked Questions
Configuration & Common Pitfalls
ignoreChanges for readCapacity and writeCapacity when you have an autoscaling policy attached.replica configuration blocks on aws.dynamodb.Table OR separate aws.dynamodb.TableReplica resources. If using TableReplica, add ignoreChanges for the replica property.hashKey, rangeKey, name, localSecondaryIndexes, restoreDateTime, or restoreToLatestTime after creating the table. Changes to these require recreating the table.Billing & Capacity
readCapacity and writeCapacity. PAY_PER_REQUEST scales automatically and doesn’t require capacity settings.billingMode is set to PROVISIONED. They’re not used with PAY_PER_REQUEST mode.Global Tables & Replication
replica blocks specifying the regionName for each region. You must enable streamEnabled: true and set streamViewType (typically NEW_AND_OLD_IMAGES) for Global Tables.consistencyMode: STRONG on each replica block. This ensures strongly consistent reads across all regions, though it comes with restrictions detailed in AWS documentation.propagateTags: true on individual replicas to inherit table tags, or use the aws.dynamodb.Tag resource to manage replica tags separately.Keys, Indexes & Attributes
globalSecondaryIndexes with hashKey, optional rangeKey, capacity settings, and projectionType. Define the GSI key attributes in the table’s attributes list.Features & Options
ttl block with attributeName (the column containing expiration timestamps) and enabled: true.streamEnabled: true and choose a streamViewType: KEYS_ONLY (only key attributes), NEW_IMAGE (entire item after change), OLD_IMAGE (before change), or NEW_AND_OLD_IMAGES (both).serverSideEncryption. For cross-region restores, you must explicitly configure encryption.Ready to get started?
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Create free account