IaC Bazaar
AWSLive-tested

DynamoDB Table

DynamoDB table with GSIs/LSIs, TTL, streams, autoscaling or on-demand, and point-in-time recovery.

terraformAWS#aws
aws-dynamodb-tableterraform v1.7

Verification

Live-tested

Really 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/dynamodb key, or your CMK via kms_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

NameTypeDefaultDescription
namestringTable name (required)
hash_key / range_keystring— / nullKey schema (must appear in attributes)
attributeslist(object)Key attributes {name, type(S|N|B)} (required)
billing_modestring"PAY_PER_REQUEST"Or PROVISIONED
read_capacity / write_capacitynumber5PROVISIONED only; initial values under autoscaling
autoscaling_enabledboolfalseTarget tracking for table + all GSIs (PROVISIONED only)
autoscaling_read / autoscaling_writeobject{}{min_capacity=5, max_capacity=100, target_utilization=70, scale_in/out_cooldown=60}
global_secondary_indexeslist(object)[]{name, hash_key, range_key, projection_type, non_key_attributes, read/write_capacity}
local_secondary_indexeslist(object)[]{name, range_key, projection_type, non_key_attributes}
ttl_attribute_namestringnullEnables TTL on that attribute
stream_enabled / stream_view_typebool / stringfalse / "NEW_AND_OLD_IMAGES"DynamoDB Streams
point_in_time_recovery_enabledbooltrue35-day continuous backups
kms_key_arnstringnullCMK for SSE; null = AWS-managed key (always encrypted)
deletion_protection_enabledbooltrueGuard against accidental destroy
table_classstring"STANDARD"Or STANDARD_INFREQUENT_ACCESS
tagsmap(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_indexes entry 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_enabled on a live table is destructive unless you migrate state first. The two modes live at two internal resource addresses (Terraform's capacity ignore_changes cannot 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 default deletion_protection_enabled = true turns 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:
    # off -> on
    terraform state mv 'module.<m>.aws_dynamodb_table.this[0]' \
                       'module.<m>.aws_dynamodb_table.autoscaled[0]'
    # on -> off: reverse the two addresses
    
    then terraform 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).