Skip to content

microsoftexpert/tf-mod-aws-dax

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🟧 AWS DAX (DynamoDB Accelerator) Terraform Module

A secure-by-default Amazon DAX cluster — TLS in transit, encryption at rest, VPC-only placement, with its subnet group and an optional dedicated parameter group — delivering an in-memory read/write cache in front of one or more DynamoDB tables from a single composite call. Built for the AWS provider v6.x.

Terraform aws module type resources


🧩 Overview

  • ⚡ Provisions an Amazon DAX cluster (aws_dax_cluster) — an in-memory, microsecond-latency cache that sits in front of DynamoDB tables via the DAX client SDK.
  • 🌐 Owns the cluster's subnet group (always created — DAX has no meaningful default placement) and an optional dedicated parameter group for TTL/query-cache tuning.
  • 🔒 Secure by default: TLS in-transit (cluster_endpoint_encryption_type = "TLS") and encryption at rest (server_side_encryption.enabled = true) are both on — opt-out, never opt-in.
  • 🧩 Consumes networking, security, and the DynamoDB-access IAM role by reference — it never creates a VPC, security group, IAM role, or DynamoDB table itself.
  • ⚠️ Unlike RDS/DynamoDB/ElastiCache, DAX's own at-rest encryption has no customer-managed-KMS option in the provider schema — see Architecture Notes.

💡 Why it matters: DAX routinely caches hot DynamoDB reads that can include PII under privacy-regulation. A cache with TLS off or sitting in the wrong subnets defeats the whole point of encrypting the table behind it — so this module ships locked down and makes you opt out explicitly.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


🗺️ Where this fits in the family

flowchart LR
 VPC["tf-mod-aws-vpc"]
 SG["tf-mod-aws-security-group"]
 IAM["tf-mod-aws-iam-role"]
 DDB["tf-mod-aws-dynamodb"]
 KMS["tf-mod-aws-kms"]
 DAX["tf-mod-aws-dax"]
 APP["Application tier<br/>ECS / EKS / EC2"]

 VPC -->|subnet_ids| DAX
 SG -->|security_group_ids| DAX
 IAM -->|iam_role_arn| DAX
 KMS -.->|CMK on the table, not DAX| DDB
 DAX -->|assumes role, proxies reads/writes| DDB
 DAX -->|configuration_endpoint| APP

 style DAX fill:#FF9900,color:#fff
Loading

DAX sits at the caching tier in front of DynamoDB. It is downstream of the networking, security-group, and IAM-role foundations, and it proxies traffic through to a DynamoDB table (which may itself be encrypted with a customer-managed KMS key — that key never touches DAX directly). The application tier talks to DAX's configuration endpoint via the DAX client SDK, which falls back to DynamoDB directly on cache misses.


🧬 What this module builds

flowchart TD
 subgraph caller["Caller-supplied (by reference)"]
 SUBNETS["subnet_ids<br/>(tf-mod-aws-vpc)"]
 SGS["security_group_ids<br/>(tf-mod-aws-security-group)"]
 ROLE["iam_role_arn<br/>(tf-mod-aws-iam-role)"]
 end

 subgraph mod["tf-mod-aws-dax"]
 SUB["aws_dax_subnet_group.this"]
 PG["aws_dax_parameter_group.this<br/>(optional)"]
 CL["aws_dax_cluster.this<br/>keystone"]
 end

 SUBNETS --> SUB
 SUB --> CL
 SGS --> CL
 ROLE --> CL
 PG --> CL

 style CL fill:#FF9900,color:#fff
Loading
Resource Role
aws_dax_cluster.this Keystone — the DAX cluster: nodes, encryption, maintenance, notifications
aws_dax_subnet_group.this Placement across caller-supplied subnets (always created)
aws_dax_parameter_group.this TTL / query-cache parameters (created only when parameter_group is set)

ℹ️ Both Mermaid diagrams above were validated with the Mermaid Chart MCP tool (validate_and_render_mermaid_diagram) during authoring — both returned valid: true.


✅ Provider / Versions

Requirement Version
Terraform >= 1.12.0
hashicorp/aws >= 6.0, < 7.0

No provider {} block is declared inside the module — it inherits the caller's configured provider (and credential chain / Region).


🔑 Required IAM Permissions

Action Required for Notes
dax:CreateCluster, dax:UpdateCluster, dax:DeleteCluster, dax:DescribeClusters Cluster lifecycle Keystone
dax:CreateSubnetGroup, dax:DeleteSubnetGroup, dax:DescribeSubnetGroups Subnet group Module-owned, always created
dax:CreateParameterGroup, dax:UpdateParameterGroup, dax:DeleteParameterGroup, dax:DescribeParameterGroups, dax:DescribeParameters Parameter group Only when parameter_group is set
dax:TagResource, dax:UntagResource, dax:ListTags Tagging Cluster only — subnet/parameter groups are not taggable
iam:PassRole (scoped to iam_role_arn) DAX assumes this role at runtime to call DynamoDB Scope to the specific role ARN, never iam:PassRole on *
iam:CreateServiceLinkedRole First-time AWSServiceRoleForDAX creation One-time per account; harmless if it already exists

⚠️ Scope the resource ARNs in your policy to arn:aws:dax:<region>:<account>:cache/* / :subnet-group/* / :parameter-group/* patterns rather than "*" where your governance allows.


📋 AWS Prerequisites

  • Service-linked role: AWSServiceRoleForDAX is auto-created on first DAX use in an account/Region (iam:CreateServiceLinkedRole) — it lets the service manage ENIs on the caller's behalf inside the VPC.
  • iam_role_arn trust + permissions policy: the IAM role DAX assumes must trust dax.amazonaws.com and carry least-privilege DynamoDB actions (GetItem, BatchGetItem, Query, Scan, PutItem, UpdateItem, DeleteItem, BatchWriteItem, ConditionCheckItem, DescribeTable) scoped to the target table(s). This module does not create that role — wire it from tf-mod-aws-iam-role.
  • VPC-only, no public endpoint concept: DAX clusters always live inside a VPC via the subnet group; there is no publicly_accessible flag to set because DAX has no public-endpoint mode at all. Security-group ingress (default port 8111) is what actually governs reachability.
  • Region constraints: none — DAX is a regional service with no us-east-1 global-service coupling.
  • Node / Region availability: DAX node types and the service itself are not available in every Region — confirm availability before targeting a new Region.
  • Service quotas: default soft limits apply per account/Region for cluster count and nodes-per-cluster; request an increase via Service Quotas if the topology needs more.

📁 Module Structure

tf-mod-aws-dax/
├── providers.tf # terraform{} + required_providers (aws >= 6.0, < 7.0); no provider{} block
├── variables.tf # typed inputs: identity → required → optional → tags → timeouts
├── main.tf # subnet group, optional parameter group, DAX cluster (this)
├── outputs.tf # id + arn, endpoints, subnet/parameter group ids, tags_all
├── SCOPE.md # boundary, IAM, prerequisites, gotchas, secure defaults
└── README.md # this file

⚙️ Quick Start

The smallest working call — networking, security, and the DynamoDB-access role wired from upstream modules:

module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-orders-cache"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.r5.large"
  replication_factor = 3
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]

  tags = {
    Environment = "prod"
    DataClass   = "PII"
    CostCenter  = "platform-data"
  }
}

Everything not shown inherits the secure baseline: TLS in-transit encryption and at-rest encryption on.


🔌 Cross-Module Contract

Consumes

Input Type Source module
subnet_ids list(string) tf-mod-aws-vpc (private subnets)
security_group_ids list(string) tf-mod-aws-security-group
iam_role_arn string (IAM role ARN) tf-mod-aws-iam-role
notification_topic_arn string (SNS ARN, optional) app-integration module

Emits

This module output Feeds into
id References to the cluster by its DAX identifier
arn tf-mod-aws-iam-role / tf-mod-aws-iam-policy (scoping dax:* actions), monitoring
name Tagging, monitoring, and DNS modules that key on resource name
configuration_endpoint Application tier — the DAX client SDK connects here for cluster discovery
cluster_address Application config that needs the bare DNS name
port tf-mod-aws-security-group ingress rules for the DAX port
nodes Monitoring / per-node routing
cluster_endpoint_encryption_type Security review / compliance evidence that TLS is actually applied
subnet_group_id Reference / import
subnet_group_vpc_id Cross-checking tf-mod-aws-vpc wiring
parameter_group_id Reference / import
tags_all Governance / audit

📚 Example Library

1 · Minimal cluster (secure defaults)
module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-orders-cache"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.r5.large"
  replication_factor = 3
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]
}

TLS in-transit and at-rest encryption are both on by default.

2 · Tags (merge with provider default_tags)
# Provider-level default_tags is the CALLER's concern (root module / pipeline):
provider "aws" {
  default_tags { tags = { ManagedBy = "terraform", Org = "" } }
}

module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-orders-cache"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.r5.large"
  replication_factor = 3
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]

  tags = {
    Environment = "prod"
    DataClass   = "PII"
    Org         = "-Data" # resource tag wins over default_tags on key conflict
  }
}
# module.dax.tags_all => { ManagedBy, Org=-Data, Environment, DataClass }
3 · Customer-managed KMS — applied to the underlying table, not DAX
# DAX's own server_side_encryption has no kms_key_arn argument in the AWS
# provider schema — its at-rest encryption is always backed by an AWS-owned
# key. For an auditable, revocable CMK, encrypt the DynamoDB table DAX
# accelerates instead:

module "kms" {
  source      = "git::https://github.com/microsoftexpert/tf-mod-aws-kms?ref=v1.0.0"
  description = "CMK for the orders DynamoDB table"
  alias       = "alias/casey-orders-table"
}

module "orders_table" {
  source     = "git::https://github.com/microsoftexpert/tf-mod-aws-dynamodb?ref=v1.0.0"
  name       = "casey-orders"
  hash_key   = "pk"
  attributes = [{ name = "pk", type = "S" }]

  server_side_encryption = {
    enabled     = true
    kms_key_arn = module.kms.arn
  }
}

module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-orders-cache"
  iam_role_arn       = module.dax_role.arn # policy scoped to module.orders_table.arn
  node_type          = "dax.r5.large"
  replication_factor = 3
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]
}

DAX's own server_side_encryption.enabled = true (default) still applies to the cache itself — the CMK secures the table it accelerates.

4 · Secure-by-default opt-out — TLS disabled (documented exception only)
module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-legacy-cache"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.t3.small"
  replication_factor = 1
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]

  # OPT-OUT — only for a legacy client that cannot negotiate TLS; document the
  # exception and prefer upgrading the client instead:
  cluster_endpoint_encryption_type = "NONE"

  tags = { Environment = "dev", DataClass = "synthetic" }
}

This value cannot be changed on an existing cluster — choose it at creation time.

5 · High-availability cluster (3 nodes, no single point of failure)
module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-orders-cache-ha"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.r5.xlarge"
  replication_factor = 3
  availability_zones = ["us-east-2a", "us-east-2b", "us-east-2c"]
  subnet_ids         = module.vpc.private_subnet_ids # spans the same 3 AZs
  security_group_ids = [module.dax_sg.id]
}
6 · Dedicated parameter group (TTL tuning)
module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-orders-cache-tuned"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.r5.large"
  replication_factor = 3
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]

  parameter_group = {
    description = "Longer TTLs for a read-heavy catalog cache"
    parameters = {
      "query-ttl-millis"  = "300000"
      "record-ttl-millis" = "300000"
    }
  }
}

The module creates <cluster_name>-params and associates it (takes precedence over parameter_group_name).

7 · Existing (externally-managed) parameter group
module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name         = "casey-catalog-cache"
  iam_role_arn         = module.dax_role.arn
  node_type            = "dax.r5.large"
  replication_factor   = 3
  subnet_ids           = module.vpc.private_subnet_ids
  security_group_ids   = [module.dax_sg.id]
  parameter_group_name = "casey-shared-dax-params" # managed outside this module call
}
8 · Notifications to SNS
module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-orders-cache"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.r5.large"
  replication_factor = 3
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]

  notification_topic_arn = module.dax_notifications.arn # tf-mod-aws-sns
  maintenance_window     = "sun:05:00-sun:09:00"
}
9 · Custom timeouts (large / slow-to-provision topology)
module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-bulk-cache"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.r5.4xlarge"
  replication_factor = 5
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]

  timeouts = {
    create = "60m"
    update = "60m"
    delete = "120m"
  }
}

Provider defaults are already generous (create/update 45m, delete 90m) — raise further only for very large clusters.

10 · for_each — a cache per microservice, at scale
locals {
  caches = {
    orders   = { node_type = "dax.r5.large", replication_factor = 3 }
    catalog  = { node_type = "dax.r5.xlarge", replication_factor = 3 }
    sessions = { node_type = "dax.t3.small", replication_factor = 1 }
  }
}

module "dax" {
  for_each = local.caches
  source   = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-${each.key}-cache"
  iam_role_arn       = module.dax_role[each.key].arn
  node_type          = each.value.node_type
  replication_factor = each.value.replication_factor
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg[each.key].id]

  tags = { Service = each.key, Environment = "prod" }
}
11 · Import an existing cluster
import {
  to = module.dax.aws_dax_cluster.this
  id = "casey-orders-cache"
}

Run terraform plan after adding the import block to reconcile any drift between the imported cluster's real configuration and the module call's arguments before applying.

12 · Least-privilege security group (application tier only)
module "dax_sg" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-security-group?ref=v1.0.0"
  name   = "casey-dax-sg"
  vpc_id = module.vpc.id

  ingress_rules = {
    dax = {
      from_port                    = 8111
      to_port                      = 8111
      ip_protocol                  = "tcp"
      referenced_security_group_id = module.app_sg.id # app tier only — never 0.0.0.0/0
      description                  = "DAX from application tier"
    }
  }
}
13 · 🏁 End-to-end composition (VPC → SG → IAM role → DynamoDB → DAX)
module "vpc" {
  source   = "git::https://github.com/microsoftexpert/tf-mod-aws-vpc?ref=v1.0.0"
  name     = "casey-data"
  vpc_cidr = "10.40.0.0/16"
  #... produces private_subnet_ids across 3 AZs
}

module "orders_table" {
  source     = "git::https://github.com/microsoftexpert/tf-mod-aws-dynamodb?ref=v1.0.0"
  name       = "casey-orders"
  hash_key   = "pk"
  attributes = [{ name = "pk", type = "S" }]
}

module "dax_role" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-role?ref=v1.0.0"
  name   = "casey-orders-dax-role"

  assume_role_policy = {
    statements = [{
      effect     = "Allow"
      principals = { type = "Service", identifiers = ["dax.amazonaws.com"] }
      actions    = ["sts:AssumeRole"]
    }]
  }

  inline_policies = {
    dynamodb-access = {
      statements = [{
        effect = "Allow"
        actions = [
          "dynamodb:GetItem", "dynamodb:BatchGetItem", "dynamodb:Query", "dynamodb:Scan",
          "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem",
          "dynamodb:BatchWriteItem", "dynamodb:ConditionCheckItem", "dynamodb:DescribeTable",
        ]
        resources = [module.orders_table.arn]
      }]
    }
  }
}

module "dax_sg" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-security-group?ref=v1.0.0"
  name   = "casey-orders-dax-sg"
  vpc_id = module.vpc.id

  ingress_rules = {
    dax = {
      from_port                    = 8111
      to_port                      = 8111
      ip_protocol                  = "tcp"
      referenced_security_group_id = module.app_sg.id
      description                  = "DAX from application tier"
    }
  }
}

module "dax" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dax?ref=v1.0.0"

  cluster_name       = "casey-orders-cache"
  iam_role_arn       = module.dax_role.arn
  node_type          = "dax.r5.large"
  replication_factor = 3
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.dax_sg.id]

  tags = {
    Environment = "prod"
    DataClass   = "PII"
    Compliance  = "privacy-regulation"
  }
}

output "dax_configuration_endpoint" { value = module.dax.configuration_endpoint }
output "dax_arn" { value = module.dax.arn }

📥 Inputs (high-level)

  • Core: cluster_name (FORCE-NEW), iam_role_arn, node_type, replication_factor
  • Networking: subnet_ids (required, always creates the subnet group), subnet_group_description, security_group_ids, availability_zones
  • Encryption: cluster_endpoint_encryption_type (default TLS), server_side_encryption (default { enabled = true }, AWS-owned key only)
  • Configuration: description, maintenance_window, notification_topic_arn
  • Parameter group: parameter_group_name (external), parameter_group (module-created, takes precedence)
  • Tags / Timeouts: tags (cluster only), timeouts (create/update/delete)

Full type schemas and per-field descriptions live in variables.tf.


🧾 Outputs

  • id — the DAX cluster id (same as cluster_name)
  • arn — the DAX cluster ARN (cross-resource reference type)
  • name — the cluster name
  • tags_all — merged tags including provider default_tags (cluster only — subnet/parameter groups are not taggable)
  • configuration_endpoint{address, port} for the DAX client SDK
  • cluster_address — bare DNS name without the port
  • port — configuration-endpoint port (default 8111)
  • nodes — per-node objects (id, address, port, availability_zone)
  • cluster_endpoint_encryption_type — the in-transit posture actually applied (NONE/TLS)
  • subnet_group_id — the subnet group's id (its name)
  • subnet_group_vpc_id — the VPC id the subnet group resolved to
  • parameter_group_id — conditionally present (try(..., null)): the module-created group's id, the caller-supplied parameter_group_name, or null for the engine default

No output in this module is sensitive = true — DAX exposes no credential-bearing attributes.


🧠 Architecture Notes

  • ARN / ID formats. arn:aws:dax:<region>:<account>:cache/<cluster_name>; id is the (lowercased) cluster_name. Subnet group and parameter group id are simply their name — neither exposes an arn attribute in the AWS provider.
  • FORCE-NEW (immutable) fields. cluster_name is the resource's import id and cannot be renamed in place. availability_zones is effectively immutable — nodes are placed at creation time; treat changes as replacement and confirm with terraform plan, since the provider schema does not explicitly annotate this argument as ForceNew.
  • No customer-managed KMS option for DAX itself. server_side_encryption is a bare enabled boolean backed only by an AWS-owned key — there is nowhere in the schema to plumb a CMK for the cluster. Apply a CMK to the underlying DynamoDB table instead (tf-mod-aws-kms + tf-mod-aws-dynamodb); see Example 3.
  • tagstags_alldefault_tags. Only aws_dax_cluster is taggable — the subnet group and parameter group have no tags argument at all. The provider's default_tags is the caller's concern (never set inside a module); on a key collision the resource tag wins. tags_all (output) is the computed union AWS actually applied.
  • Eventual consistency / long operations. Cluster creation, node replacement, and teardown are slow relative to most AWS resources — the provider's own default timeouts are create/update 45m and delete 90m. The module only renders a timeouts {} block when the caller supplies one; otherwise these defaults apply silently.
  • Destroy ordering. The cluster must be destroyed before its subnet group or parameter group can be removed; Terraform's dependency graph orders this automatically. The module-created parameter group uses create_before_destroy so name/description changes don't deadlock.
  • parameter_group_name is in-place updatable (not FORCE-NEW) — DAX applies a new parameter group without recreating the cluster, though individual parameter changes may require a node reboot depending on that parameter's apply type, which the Terraform resource does not surface.
  • No us-east-1 constraint. DAX is a regional service — none of the us-east-1 global-service rules (CloudFront/WAF/ACM) apply.

🧱 Design Principles

Secure by default; every weakening is an explicit, documented opt-out.

Posture Default How to opt out
In-transit encryption (TLS) cluster_endpoint_encryption_type = "TLS" "NONE" (discouraged; cannot be changed after creation)
At-rest encryption server_side_encryption.enabled = true false (discouraged; AWS-owned key either way — no CMK option exists)
Network placement VPC-only via caller-supplied private subnet_ids; module always creates the subnet group n/a — DAX has no public-endpoint concept to block
Ingress security_group_ids caller-supplied; module never defaults to 0.0.0.0/0 n/a
High availability caller chooses replication_factor (module does not force a minimum) set replication_factor = 1 for a single node (no read replicas)

Other principles: exactly four .tf files; one keystone (aws_dax_cluster.this) plus a subnet group (always created) and an optional for_each-rendered parameter group (never count); deeply-typed object schemas with optional defaults; validation {} on every closed value set; no credential or region variables; primary outputs id + arn; tags_all surfaced.


🚀 Runbook

# Validate (no credentials needed)
terraform init -backend=false
terraform validate
terraform fmt -check

# Plan / apply (requires AWS credentials + Region)
# credentials via AWS_PROFILE / SSO / OIDC; Region via the provider block
terraform plan -out tfplan
terraform apply tfplan

⚠️ Pin the module source with ?ref=v1.0.0 — never a branch. plan/apply require a valid credential chain (profile / SSO / OIDC web-identity) and a configured Region. The module declares no provider {} block — supply it (and any assume_role) at the root. Cluster creation can take many minutes; raise timeouts.create for large multi-node topologies.


🧪 Testing

  • terraform init -backend=false && terraform validate — schema and reference integrity.
  • terraform fmt -check — canonical formatting.
  • terraform plan against a sandbox account — confirm secure defaults render (cluster_endpoint_encryption_type = "TLS", server_side_encryption.enabled = true) and that the subnet group is always created.
  • Post-apply smoke test: from an in-VPC host in an allowed security group, connect with the DAX client SDK to configuration_endpoint, issue a GetItem, and confirm a cache hit/miss cycle against the target DynamoDB table.

💬 Example Output

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

arn = "arn:aws:dax:us-east-2:123456789012:cache/casey-orders-cache"
id = "casey-orders-cache"
configuration_endpoint = "casey-orders-cache.abc123.dax-clusters.use2.cache.amazonaws.com:8111"
cluster_endpoint_encryption_type = "TLS"

🔍 Troubleshooting

Symptom Likely cause Fix
Tag drift on every plan default_tags overlaps a key the module also sets Drop the duplicate from one side; resource tags win — reconcile in the root module
AccessDenied on dax:CreateCluster Identity lacks the dax: actions Attach the Required IAM Permissions
AccessDenied / cluster stuck creating, DAX can't reach DynamoDB iam_role_arn's trust or permissions policy is missing or scoped wrong Confirm the role trusts dax.amazonaws.com and grants the DynamoDB actions against the target table's ARN
InvalidParameterValue:... AWSServiceRoleForDAX SLR not yet created and iam:CreateServiceLinkedRole missing Grant iam:CreateServiceLinkedRole, or pre-create the SLR
Cluster shows full replacement on plan cluster_name or availability_zones changed (immutable) Revert the change, or accept the replacement (data loss — the cache holds no durable state, but connections/endpoints change)
Application can't reach the configuration endpoint Security group doesn't allow port 8111 from the app tier, or wrong subnets Add an ingress rule scoped to the app security group; verify subnet_ids are reachable from the app tier
Client fails TLS handshake cluster_endpoint_encryption_type = "NONE" but client expects TLS (or vice versa) Align the client SDK's TLS setting with the cluster's cluster_endpoint_encryption_type
Parameter change has no effect Some DAX parameters require a node reboot to apply Check the parameter's apply type in the AWS console/API; a rolling maintenance-window reboot may be required
Credential-chain errors on plan/apply No profile/SSO/OIDC resolved, or wrong Region Set AWS_PROFILE / assume the role; confirm the provider Region
Destroy hangs on subnet/parameter group A lingering cluster still references it (half-failed destroy) Remove the cluster first; Terraform's graph normally orders this

🔗 Related Docs

  • Amazon DynamoDB Accelerator (DAX) Developer Guide
  • DAX cluster ARN and concepts reference
  • Customizing DAX cluster settings (AZ preference, parameter groups, maintenance windows)
  • Terraform aws_dax_cluster resource reference
  • Terraform aws_dax_subnet_group resource reference
  • Terraform aws_dax_parameter_group resource reference
  • Module SCOPE.md — boundary, IAM, prerequisites, gotchas
  • Upstream: tf-mod-aws-vpc, tf-mod-aws-security-group, tf-mod-aws-iam-role, tf-mod-aws-dynamodb, tf-mod-aws-kms

🧡 "Infrastructure as Code should be standardized, consistent, and secure."

Packages

 
 
 

Contributors

Languages