DynamoDB Table
DynamoDB table with GSIs/LSIs, TTL, streams, autoscaling or on-demand, and point-in-time recovery.
Verification
Live-testedReally deployed, verified, idempotent and destroyed in a cloud sandbox.
Conformance
- Static validation (fmt · validate · tflint)
- Security scan clean (Checkov)
- Plan tests (mocked: validation rules · outputs)
Provenance
- SHA-256 checksum
- Signature (pending)
Functional
- Live-tested — applied, verified, destroyed
Last verified 2026-06-11 · how we verify
Documentation
aws-dynamodb-table
DynamoDB table with GSIs/LSIs, TTL, streams, autoscaling or on-demand, and
point-in-time recovery. Works with Terraform and OpenTofu (>= 1.6), AWS
provider >= 6.0, < 7.0. The module encodes the classic DynamoDB/Terraform
trap: when target-tracking autoscaling owns provisioned capacity, the table is
created with ignore_changes on read/write capacity so plans stay clean —
without autoscaling, capacity stays fully Terraform-managed. Exactly one table
resource is created either way.
Secure defaults:
- Server-side encryption always on (AWS-managed
aws/dynamodbkey, or your CMK viakms_key_arn) - Point-in-time recovery enabled by default
- Deletion protection enabled by default
- On-demand (
PAY_PER_REQUEST) billing by default — no capacity to misplan
Usage
module "events" {
source = "./aws-dynamodb-table"
name = "events"
hash_key = "pk"
range_key = "sk"
attributes = [
{ name = "pk", type = "S" },
{ name = "sk", type = "S" },
{ name = "gsi1pk", type = "S" },
]
global_secondary_indexes = [
{ name = "gsi1", hash_key = "gsi1pk" }
]
ttl_attribute_name = "expires_at"
stream_enabled = true
tags = { Environment = "prod" }
}
Provisioned with autoscaling:
billing_mode = "PROVISIONED"
autoscaling_enabled = true
autoscaling_read = { min_capacity = 5, max_capacity = 500 }
autoscaling_write = { min_capacity = 5, max_capacity = 200 }
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
name | string | — | Table name (required) |
hash_key / range_key | string | — / null | Key schema (must appear in attributes) |
attributes | list(object) | — | Key attributes {name, type(S|N|B)} (required) |
billing_mode | string | "PAY_PER_REQUEST" | Or PROVISIONED |
read_capacity / write_capacity | number | 5 | PROVISIONED only; initial values under autoscaling |
autoscaling_enabled | bool | false | Target tracking for table + all GSIs (PROVISIONED only) |
autoscaling_read / autoscaling_write | object | {} | {min_capacity=5, max_capacity=100, target_utilization=70, scale_in/out_cooldown=60} |
global_secondary_indexes | list(object) | [] | {name, hash_key, range_key, projection_type, non_key_attributes, read/write_capacity} |
local_secondary_indexes | list(object) | [] | {name, range_key, projection_type, non_key_attributes} |
ttl_attribute_name | string | null | Enables TTL on that attribute |
stream_enabled / stream_view_type | bool / string | false / "NEW_AND_OLD_IMAGES" | DynamoDB Streams |
point_in_time_recovery_enabled | bool | true | 35-day continuous backups |
kms_key_arn | string | null | CMK for SSE; null = AWS-managed key (always encrypted) |
deletion_protection_enabled | bool | true | Guard against accidental destroy |
table_class | string | "STANDARD" | Or STANDARD_INFREQUENT_ACCESS |
tags | map(string) | {} | Tags for all resources |
Outputs
table_name, table_id, table_arn, stream_arn, stream_label.
Immutability traps (read before changing a live table)
- LSIs are creation-only: adding/removing a
local_secondary_indexesentry forces full table replacement (data loss without a backup/restore). - GSI key schema/projection changes delete and recreate that index (table data is safe; the index rebuilds, which takes time on large tables).
- Switching
autoscaling_enabledon a live table is destructive unless you migrate state first. The two modes live at two internal resource addresses (Terraform's capacityignore_changescannot be conditional, so each mode needs its own resource). Toggling the flag plans a destroy + recreate of the table — i.e. data loss — not an in-place edit. The defaultdeletion_protection_enabled = trueturns that into an apply-time error (table preserved) rather than silent data loss, but with deletion protection off the data is gone. Before toggling on a deployed PROVISIONED table, run:
then# off -> on terraform state mv 'module.<m>.aws_dynamodb_table.this[0]' \ 'module.<m>.aws_dynamodb_table.autoscaled[0]' # on -> off: reverse the two addressesterraform apply(it will only create/destroy the autoscaling resources, leaving the table in place). - Every attribute referenced by a key or index must stay in
attributes.
Requirements
- Terraform or OpenTofu
>= 1.6 hashicorp/aws>= 6.0, < 7.0
Verification
Static-validated (fmt, validate, tflint). Live apply/destroy testing pending cloud sandbox availability — see catalog status.
License
Commercial — IaC Bazaar EULA. © IaC Bazaar. Original work (not derived from a third-party module).