Skip to content

Latest commit

Β 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🟧 AWS MemoryDB Terraform Module

A secure-by-default Amazon MemoryDB cluster β€” a durable, in-memory database for Redis/Valkey workloads β€” always encrypted at rest, always TLS-required in transit, RBAC-gated through a named ACL, and snapshot-backed, delivering the cluster, its subnet group, parameter group, RBAC users, and ACL from a single composite call. Built for the AWS provider v6.x.

Terraform aws module type resources


🧩 Overview

  • ⚑ Provisions an Amazon MemoryDB cluster β€” the keystone is aws_memorydb_cluster.this. Unlike ElastiCache, MemoryDB is a durable primary database: writes are persisted to a Multi-AZ transactional log, so it can be a system of record, not just a cache.
  • 🌐 Owns the subnet group (<name>-subnets, private subnets, β‰₯ 2 AZs for a multi-shard / multi-replica topology) so a single call yields a complete, private placement.
  • πŸ‘₯ Optionally creates RBAC users (aws_memorydb_user) as a for_each map keyed by user_id, bundled into a module-created named ACL (aws_memorydb_acl) and bound to the cluster β€” the secure path that replaces the discouraged built-in open-access ACL.
  • πŸŽ›οΈ Optionally creates a dedicated parameter group (<name>-params) for engine tuning (maxmemory-policy, etc.).
  • πŸ”’ Secure by default: at-rest encryption is always on (AWS-managed key, or a caller CMK), in-transit TLS is always on and cannot be disabled, access defaults to a named RBAC ACL (never open-access), one replica per shard for automatic failover, and 7-day snapshot retention.
  • 🧩 Consumes networking, security, and KMS by reference β€” it never creates a VPC, security group, or KMS key itself; secrets (RBAC passwords) are referenced from Secrets Manager rather than inlined.

πŸ’‘ Why it matters: Because MemoryDB is durable, it frequently holds the authoritative copy of session state, tokens, and cached PII under privacy-regulation β€” the blast radius of a misconfigured cluster is larger than a throwaway cache. This module ships locked down (mandatory encryption, mandatory TLS, RBAC ACL) and makes you opt out of the few relaxable defaults 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["terraform-aws-vpc"]
 SG["terraform-aws-security-group"]
 KMS["terraform-aws-kms"]
 SM["terraform-aws-secrets-manager"]
 MDB["terraform-aws-memorydb"]
 APP["Application tier<br/>(ECS / EKS / EC2)"]

 VPC -->|subnet_ids| MDB
 SG -->|security_group_ids| MDB
 KMS -->|kms_key_arn| MDB
 SM -->|RBAC user passwords| MDB
 MDB -->|cluster configuration endpoint| APP

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

MemoryDB sits at the durable data tier. It is downstream of the networking, security-group, and KMS foundations and upstream of the application tier that connects to its cluster (configuration) endpoint. Secrets Manager supplies RBAC user passwords.


🧬 What this module builds

flowchart TD
 subgraph caller["Caller-supplied (by reference)"]
 SUBNETS["subnet_ids<br/>(terraform-aws-vpc)"]
 SGS["security_group_ids<br/>(terraform-aws-security-group)"]
 CMK["kms_key_arn<br/>(terraform-aws-kms)"]
 SECRET["user passwords<br/>(terraform-aws-secrets-manager)"]
 end

 subgraph mod["terraform-aws-memorydb"]
 SUB["aws_memorydb_subnet_group.this"]
 PG["aws_memorydb_parameter_group.this<br/>(optional)"]
 U["aws_memorydb_user.this<br/>for_each β€” RBAC users"]
 ACL["aws_memorydb_acl.this<br/>(optional, binds users)"]
 CL["aws_memorydb_cluster.this<br/>keystone"]
 end

 SUBNETS --> SUB
 SUB --> CL
 PG --> CL
 SGS --> CL
 CMK --> CL
 SECRET --> U
 U --> ACL
 ACL --> CL

 style CL fill:#FF9900,color:#fff
Loading
Resource Role
aws_memorydb_cluster.this Keystone β€” the durable in-memory cluster. Encryption, topology, backups, ACL binding
aws_memorydb_subnet_group.this Placement across caller-supplied private subnets (<name>-subnets, β‰₯ 2 AZs)
aws_memorydb_parameter_group.this Engine parameters (created only when parameter_group is set; <name>-params)
aws_memorydb_user.this RBAC users, for_each over var.users keyed by user_id
aws_memorydb_acl.this Named ACL binding the RBAC users to the cluster (created only when acl is set; <name>-acl)

βœ… 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). See the AWS Prerequisites Region note below.


πŸ”‘ Required IAM Permissions

The Terraform identity needs the following (least-privilege). RBAC, ACL, parameter-group, and KMS actions are needed only when their respective features are configured.

Action Required for Notes
memorydb:CreateCluster, memorydb:UpdateCluster, memorydb:DeleteCluster, memorydb:DescribeClusters Cluster lifecycle Keystone
memorydb:CreateSubnetGroup, memorydb:UpdateSubnetGroup, memorydb:DeleteSubnetGroup, memorydb:DescribeSubnetGroups Subnet group Module-owned
memorydb:CreateParameterGroup, memorydb:UpdateParameterGroup, memorydb:DeleteParameterGroup, memorydb:DescribeParameterGroups Parameter group Only when parameter_group is set
memorydb:CreateUser, memorydb:UpdateUser, memorydb:DeleteUser, memorydb:DescribeUsers RBAC users Only when users is set
memorydb:CreateACL, memorydb:UpdateACL, memorydb:DeleteACL, memorydb:DescribeACLs Named ACL Only when acl is set
memorydb:TagResource, memorydb:UntagResource, memorydb:ListTags Tagging All taggable resources
memorydb:CreateSnapshot, memorydb:DescribeSnapshots Final / automatic snapshots final_snapshot_name on destroy; snapshot_retention_limit
kms:DescribeKey, kms:CreateGrant, kms:RetireGrant CMK at-rest encryption Only when kms_key_arn (CMK) is supplied
iam:CreateServiceLinkedRole First-time AWSServiceRoleForMemoryDB creation One-time per account; harmless if it already exists
ec2:CreateNetworkInterface, ec2:DescribeSubnets, ec2:DescribeSecurityGroups, ec2:DescribeVpcs VPC placement / ENI provisioning Performed by the service via the SLR; describe calls validate the subnet group

⚠️ Scope the resource ARNs in your policy to arn:aws:memorydb:<region>:<account>:cluster/* / :subnetgroup/* / :parametergroup/* / :user/* / :acl/* patterns rather than "*" where your governance allows.


πŸ“‹ AWS Prerequisites

  • Service-linked role: AWSServiceRoleForMemoryDB is auto-created on first MemoryDB use (iam:CreateServiceLinkedRole). It lets the service manage ENIs and resources inside your VPC.
  • Networking: the supplied subnet_ids must be private (MemoryDB has no public endpoint β€” PII/privacy-regulation baseline) and span at least two Availability Zones for any multi-shard or multi-replica cluster (the default is one replica per shard). The security groups must allow the cluster port (6379 by default) from the application security group only β€” never 0.0.0.0/0.
  • Mandatory at-rest encryption: MemoryDB is always encrypted at rest β€” there is no toggle. Supplying kms_key_arn selects a customer-managed key; omitting it uses the AWS-managed key. A CMK's key policy must allow the MemoryDB service principal (memorydb.amazonaws.com) the standard kms:Encrypt/Decrypt/GenerateDataKey*/CreateGrant set. Changing kms_key_arn is FORCE-NEW.
  • Mandatory in-transit encryption (TLS): unlike ElastiCache, MemoryDB cannot disable TLS β€” it is always on. RBAC therefore always operates over an encrypted channel.
  • ACL required: every cluster must reference an ACL. The built-in open-access ACL disables RBAC and is strongly discouraged for PII/privacy-regulation workloads β€” define RBAC users via users and let the module create a named ACL (acl). Provide exactly one of acl or acl_name.
  • Engine / node availability: the chosen engine (redis/valkey), engine_version, and node_type must be offered in the target Region. MemoryDB uses the db.* node family (e.g. db.t4g.small, db.r7g.large), not the ElastiCache cache.* family. data_tiering = true requires an r6gd node type.
  • Region model: the module relies on provider inheritance β€” there is no region variable. The caller's provider block (or alias) sets the Region. MemoryDB is a regional service β€” no us-east-1 global-service constraint applies.
  • Service quotas: default soft limits on nodes/clusters per Region, all raisable via Service Quotas. A ClusterQuotaForCustomerExceededFault indicates the per-account cluster quota was hit.

πŸ“ Module Structure

terraform-aws-memorydb/
β”œβ”€β”€ 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, parameter group, RBAC users, ACL, cluster (this)
β”œβ”€β”€ outputs.tf # id + arn, endpoints, RBAC/ACL maps, subnet/parameter group, tags_all
β”œβ”€β”€ SCOPE.md # boundary, IAM, prerequisites, gotchas, secure defaults
└── README.md # this file

βš™οΈ Quick Start

The smallest working call β€” networking, security, and RBAC wired from upstream modules:

module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-core-mdb"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]

  users = {
    app-rw = {
      user_name     = "app-rw"
      access_string = "on ~* &* +@all"
      authentication_mode = {
        type      = "password"
        passwords = [data.aws_secretsmanager_secret_version.app_rw.secret_string] # sensitive
      }
    }
  }
  acl = {} # module creates `<name>-acl` and binds the users above

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

Everything not shown inherits the secure baseline: at-rest encryption on (always), TLS on (always), one replica per shard for automatic failover, and 7-day snapshot retention.


πŸ”Œ Cross-Module Contract

Consumes

Input Type Source module
subnet_ids list(string) terraform-aws-vpc (private subnets, β‰₯ 2 AZs)
security_group_ids list(string) terraform-aws-security-group
kms_key_arn string (KMS key ARN, optional) terraform-aws-kms
user passwords string (sensitive, optional) terraform-aws-secrets-manager
sns_topic_arn string (SNS ARN, optional) app-integration module

Emits

Output Description Consumed by
id Cluster id (same as name) references
arn Cluster ARN β€” cross-resource reference type IAM policies, monitoring, AWS Backup
name Cluster name CLI / console / app config
engine redis / valkey conditionals
engine_patch_version Running engine patch version drift / audit
cluster_endpoint_address Cluster (configuration) endpoint host β€” the client connection target application config
cluster_endpoint_port Cluster port application config
shards Shard topology (name, num_nodes, slots, per-node endpoints) monitoring
acl_name ACL bound to the cluster (module-created or external) references
acl_arn ARN of the module-created ACL (null when external) references
user_arns Map of user_id β†’ ARN for module-created RBAC users audit
subnet_group_name / subnet_group_arn Module-created subnet group references
parameter_group_name / parameter_group_arn Parameter group (module-created, external, or engine default) references
tags_all All tags incl. provider default_tags governance / audit

πŸ“š Example Library

1 Β· Minimal (secure defaults, RBAC ACL)
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-min"
  node_type          = "db.t4g.small"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]

  users = {
    app = {
      user_name     = "app"
      access_string = "on ~* &* +@all"
      authentication_mode = {
        type      = "password"
        passwords = [data.aws_secretsmanager_secret_version.app.secret_string]
      }
    }
  }
  acl = {} # creates `casey-mdb-min-acl`
}

At-rest encryption, TLS, one replica per shard, and 7-day snapshots are all on by default.

2 Β· Customer-managed KMS key (CMK) for at-rest encryption
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-cmk"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]

  kms_key_arn = module.kms.arn # auditable, independently-revocable CMK

  acl_name = "open-access" # (shown for brevity; prefer a named ACL β€” see example 1)
}

MemoryDB is always encrypted at rest. Supplying kms_key_arn upgrades the AWS-managed key to your CMK. Changing it later is FORCE-NEW.

3 Β· 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 "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-tagged"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]
  acl_name           = "open-access"

  tags = {
    Environment = "prod"
    DataClass   = "PII"
    Org         = "-Data" # resource tag wins over default_tags on key conflict
  }
}
# module.memorydb.tags_all => { ManagedBy, Org=-Data, Environment, DataClass }
4 Β· Sharded cluster (cluster mode)
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-sharded"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids # 3 AZs
  security_group_ids = [module.mdb_sg.id]

  num_shards             = 3
  num_replicas_per_shard = 2 # 3 shards Γ— (1 primary + 2 replicas) = 9 nodes

  acl = {}
  users = {
    app = {
      user_name           = "app"
      access_string       = "on ~* &* +@all"
      authentication_mode = { type = "password", passwords = [data.aws_secretsmanager_secret_version.app.secret_string] }
    }
  }
}

Clients connect to the cluster configuration endpoint for shard auto-discovery.

5 Β· Multiple RBAC users (read-write + read-only) in a named ACL
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-rbac"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]

  users = {
    app-rw = {
      user_name           = "app-rw"
      access_string       = "on ~* &* +@all"
      authentication_mode = { type = "password", passwords = [data.aws_secretsmanager_secret_version.app_rw.secret_string] }
    }
    app-ro = {
      user_name           = "app-ro"
      access_string       = "on ~* &* +@read"
      authentication_mode = { type = "password", passwords = [data.aws_secretsmanager_secret_version.app_ro.secret_string] }
    }
  }

  acl = {
    name       = "casey-mdb-rbac-acl"
    user_names = ["audit-readonly"] # externally-managed user, added alongside module users
  }
}

Every key in users is added to the ACL automatically; acl.user_names appends externally-managed user names.

6 Β· IAM-authenticated RBAC user
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-iamauth"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]

  users = {
    app = {
      user_name           = "app"
      access_string       = "on ~* &* +@all"
      authentication_mode = { type = "iam" } # no passwords β€” auth via IAM
    }
  }
  acl = {}
}

type = "iam" lets clients authenticate with IAM identities β€” no password to store. Prefer this where the client runtime already has an IAM principal.

7 Β· Custom parameter group (module-created)
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-params"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]
  acl                = {}

  parameter_group = {
    family      = "memorydb_redis7"
    description = "MemoryDB Redis 7 tuned parameters"
    parameters = {
      "maxmemory-policy" = "allkeys-lru"
    }
  }
}

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

8 Β· Valkey engine
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-valkey"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]

  engine          = "valkey"
  engine_version  = "7.3"
  acl             = {}
  parameter_group = { family = "memorydb_valkey7" }
}

Valkey is the Redis-compatible, lower-cost engine β€” RBAC, encryption, and durability apply identically. Match the parameter-group family to the engine.

9 Β· Custom snapshot + maintenance windows, 35-day retention, final snapshot
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-backups"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]
  acl                = {}

  snapshot_retention_limit = 35
  snapshot_window          = "04:00-05:00"         # UTC
  maintenance_window       = "sun:05:30-sun:06:30" # UTC
  final_snapshot_name      = "casey-mdb-backups-final"
}
10 Β· Seed a new cluster from an existing snapshot
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-restored"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]
  acl                = {}

  snapshot_name = "casey-core-mdb-2026-06-18" # FORCE-NEW; or seed from S3 via snapshot_arns
}
11 Β· SNS notifications + auto minor-version upgrades
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-notify"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]
  acl                = {}

  sns_topic_arn              = module.mdb_events_topic.arn # app-integration module
  auto_minor_version_upgrade = true                        # default
}
12 Β· Reference an existing, externally-managed ACL
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-extacl"
  node_type          = "db.r7g.large"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]

  # No `acl` / `users` here β€” bind to an ACL managed elsewhere:
  acl_name = "platform-shared-acl"
}

Provide exactly one of acl (module-created) or acl_name (external).

13 Β· Disposable dev cluster (resilience defaults relaxed)
module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-mdb-dev"
  node_type          = "db.t4g.small"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]
  acl                = {}

  # OPT-OUTS β€” dev only, never in prod / PII:
  num_replicas_per_shard   = 0 # single node per shard, no automatic failover
  snapshot_retention_limit = 0 # disables automatic snapshots

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

At-rest encryption and TLS remain on β€” they cannot be disabled on MemoryDB, even for a disposable cluster.

14 Β· 🏁 End-to-end composition (VPC β†’ SG β†’ KMS β†’ Secrets β†’ MemoryDB)
module "vpc" {
  source   = "git::https://github.com/microsoftexpert/terraform-aws-vpc?ref=v1.0.0"
  name     = "casey-data"
  vpc_cidr = "10.40.0.0/16"
  #... produces private_subnet_ids across 3 AZs
}

module "kms" {
  source      = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
  description = "CMK for MemoryDB at-rest encryption"
  alias       = "alias/casey-mdb"
}

module "mdb_sg" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-security-group?ref=v1.0.0"
  name   = "casey-mdb-sg"
  vpc_id = module.vpc.id

  ingress_rules = {
    memorydb = {
      from_port                    = 6379
      to_port                      = 6379
      ip_protocol                  = "tcp"
      referenced_security_group_id = module.app_sg.id # app tier only β€” never 0.0.0.0/0
      description                  = "MemoryDB from application tier"
    }
  }
}

data "aws_secretsmanager_secret_version" "mdb_app" {
  secret_id = "casey/memorydb/app-rw"
}

module "memorydb" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-memorydb?ref=v1.0.0"

  name               = "casey-core-mdb"
  node_type          = "db.r7g.large"
  engine             = "redis"
  engine_version     = "7.1"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.mdb_sg.id]

  kms_key_arn = module.kms.arn

  num_shards             = 2
  num_replicas_per_shard = 1

  users = {
    app-rw = {
      user_name           = "app-rw"
      access_string       = "on ~* &* +@all"
      authentication_mode = { type = "password", passwords = [data.aws_secretsmanager_secret_version.mdb_app.secret_string] }
    }
  }
  acl = { name = "casey-core-mdb-acl" }

  snapshot_retention_limit = 14
  final_snapshot_name      = "casey-core-mdb-final"

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

output "mdb_endpoint" { value = module.memorydb.cluster_endpoint_address }
output "mdb_arn" { value = module.memorydb.arn }

πŸ“₯ Inputs

Name Type Default Description
name string β€” (required) Cluster name; base name for subnet/parameter group + ACL. FORCE-NEW. 1–40 chars, lowercase
node_type string β€” (required) Node instance class β€” db.* family (e.g. db.r7g.large). r6gd required for data tiering
subnet_ids list(string) β€” (required) Subnet group members (private, β‰₯ 2 AZs for multi-shard/replica)
security_group_ids list(string) [] Security groups attached to the cluster
acl object null Module-creates a named ACL (<name>-acl) binding users + acl.user_names. Exactly one of acl / acl_name
acl_name string null Existing external ACL to associate. open-access disables RBAC (discouraged)
users map(object) {} RBAC users keyed by user_id; each has user_name, access_string, authentication_mode
engine string null (β†’ redis) redis / valkey
engine_version string null Engine version; null = latest at create. Downgrades unsupported
port number null (β†’ 6379) Cluster port. FORCE-NEW
num_shards number 1 Shards (data partitions)
num_replicas_per_shard number 1 Replicas per shard (0–5); 1 = HA baseline
data_tiering bool false Data tiering (r6gd only). FORCE-NEW
kms_key_arn string null CMK for at-rest encryption (else AWS-managed key). FORCE-NEW
tls_enabled bool true In-transit TLS. FORCE-NEW; MemoryDB does not support disabling it in practice
parameter_group_name string null Existing parameter group to associate
parameter_group object null Module-creates a dedicated parameter group <name>-params
snapshot_retention_limit number 7 Snapshot retention days (0–35; 0 disables)
snapshot_window string null Daily UTC snapshot window
snapshot_arns / snapshot_name list/string null Seed / restore from snapshot. FORCE-NEW
final_snapshot_name string null Final snapshot on destroy
maintenance_window string null Weekly UTC maintenance window
auto_minor_version_upgrade bool true Auto minor-version upgrades. FORCE-NEW
sns_topic_arn string null SNS topic for cluster notifications
ip_discovery / network_type string null IP version / dual-stack (network_type FORCE-NEW)
multi_region_cluster_name string null Multi-region cluster membership
description string null Cluster description
subnet_group_description string "Managed by Terraform" Subnet group description
tags map(string) {} Tags merged onto all taggable resources
timeouts object {} create / update / delete timeouts

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


🧾 Outputs

See the Emits table above. Primary outputs are id and arn; connectivity is exposed as cluster_endpoint_address / cluster_endpoint_port; shard topology as shards; RBAC/ACL data as acl_name / acl_arn / user_arns; the module-created groups as subnet_group_name/_arn and parameter_group_name/_arn; tags_all reflects the merged tag set. No secret is emitted β€” RBAC passwords are referenced from Secrets Manager, never output.


🧠 Architecture Notes

  • ARN / ID formats.
  • Cluster arn: arn:aws:memorydb:<region>:<account>:cluster/<name>; id = the cluster name.
  • Subnet group arn: :subnetgroup/<name>-subnets; parameter group arn: :parametergroup/<name>-params; RBAC user arn: :user/<user_name>; ACL arn: :acl/<name>-acl.
  • Durable, not a cache. MemoryDB persists writes to a Multi-AZ transactional log, so it can be a primary database. This is why the secure posture (mandatory encryption + TLS, RBAC ACL, snapshots, β‰₯1 replica) is enforced more tightly than terraform-aws-elasticache.
  • FORCE-NEW (immutable) fields. name, subnet_group_name membership, port, kms_key_arn, network_type, data_tiering, auto_minor_version_upgrade, tls_enabled, an engine-version downgrade, and snapshot_arns/snapshot_name all destroy-and-recreate the cluster (and its data). The module-created parameter group and ACL use create_before_destroy so family/membership changes don't deadlock.
  • Encryption and TLS are mandatory. Unlike ElastiCache there is no at_rest_encryption_enabled toggle and no way to turn TLS off β€” kms_key_arn only chooses which key encrypts at rest (CMK vs AWS-managed). tls_enabled defaults to true and the service requires it for RBAC.
  • ACL coupling. The cluster's acl_name must reference an existing ACL. The module computes effective_acl_name from the module-created aws_memorydb_acl.this (when acl is set) or the external acl_name, and the ACL's membership is the union of every aws_memorydb_user.this plus acl.user_names. Create users and the ACL before/alongside the cluster β€” the dependency graph orders this.
  • tags ↔ tags_all ↔ default_tags. The module sets only resource-level tags (merged with per-item tags on users/ACL via merge(var.tags, try(each.value.tags, {}))). 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 β€” use it for drift checks and audit.
  • Eventual consistency. Cluster and node creation is asynchronous and can take many minutes; the cluster_endpoint_address is a stable DNS name that follows failover, so applications should resolve it at connect time rather than caching IPs.
  • Destroy ordering. Terraform tears down the cluster β†’ ACL β†’ users / parameter group / subnet group. A subnet group, parameter group, or ACL cannot be deleted while the cluster still references it; the dependency graph normally orders this, but a half-failed destroy can leave a group/ACL pinned by a lingering cluster. With final_snapshot_name set, a final snapshot is taken before deletion. The service-managed ENIs are cleaned up by the SLR β€” there are no NAT/ENI destroy hazards to manage directly.
  • No us-east-1 constraint. MemoryDB is a regional service β€” none of the us-east-1 global-service rules (CloudFront/WAF/ACM) apply. Rely on provider inheritance for the Region.

🧱 Design Principles

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

Posture Default How to opt out
At-rest encryption always on (AWS-managed key) n/a β€” cannot be disabled
Customer-managed key available via kms_key_arn omit for the AWS-managed key
In-transit TLS tls_enabled = true, always on n/a β€” MemoryDB does not allow disabling TLS
Access control named RBAC ACL via acl + users acl_name = "open-access" (strongly discouraged)
Public exposure private subnets only β€” no public endpoint n/a
Automatic failover / HA num_replicas_per_shard = 1 (replica in another AZ) 0 (single node per shard; documented exception)
Backups snapshot_retention_limit = 7 0 (disables snapshots; discouraged)
Final snapshot on destroy recommended via final_snapshot_name leave null (skips it)

Other principles: exactly four .tf files; one keystone named this; child collections (users, ACL, parameter group) via for_each over map(object) (never count); deeply-typed object schemas with optional defaults; validation {} on every closed value set (engine, network_type, ip_discovery, authentication_mode.type, snapshot/replica ranges); no credential or region variables; secrets referenced not stored; 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

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 several minutes; raise timeouts.create for large sharded topologies.

⚠️ Always pin the module source with ?ref=v1.0.0 β€” never a branch.


πŸ§ͺ Testing

  • terraform init -backend=false && terraform validate β€” schema and reference integrity.
  • terraform fmt -check β€” canonical formatting.
  • terraform plan against a sandbox account β€” confirm the secure defaults render (at-rest encryption + TLS on, one replica per shard, 7-day snapshots) and that the ACL is the module-created named ACL rather than open-access.
  • Post-apply smoke test: connect over TLS from an in-VPC host to the cluster_endpoint_address on 6379, authenticate with an RBAC user, and run PING / INFO replication.

πŸ’¬ Example Output

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

Outputs:

arn = "arn:aws:memorydb:us-east-2:123456789012:cluster/casey-core-mdb"
id = "casey-core-mdb"
mdb_endpoint = "clustercfg.casey-core-mdb.abc123.memorydb.us-east-2.amazonaws.com"
acl_name = "casey-core-mdb-acl"

πŸ” 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 memorydb:CreateCluster Identity lacks the memorydb: actions Attach the Required IAM Permissions
InvalidParameterValue:... AWSServiceRoleForMemoryDB SLR not yet created and iam:CreateServiceLinkedRole missing Grant iam:CreateServiceLinkedRole, or pre-create the SLR
ACLNotFound / cluster create rejects acl_name The ACL doesn't exist yet, or both acl and acl_name were set Provide exactly one of acl (module-created) or acl_name (existing)
ACL create fails β€” user not found A name in acl.user_names isn't a real MemoryDB user Create the user first (or via users); only externally-managed names go in user_names
users[*].authentication_mode.type validation error Value other than password / iam Use password (with passwords) or iam (no password)
Changing the KMS key triggers full replacement kms_key_arn is FORCE-NEW Choose the CMK at creation; migrating keys requires a rebuild/restore
Parameter-group family mismatch family doesn't match engine/engine_version (e.g. memorydb_redis7 vs Valkey) Set family to the matching engine family (memorydb_valkey7, etc.)
Node type rejected Used an ElastiCache cache.* type MemoryDB uses the db.* family β€” e.g. db.r7g.large
Subnet/parameter group or ACL won't delete A lingering cluster still references it (half-failed destroy) Remove the cluster first; Terraform's graph normally orders this
ClusterQuotaForCustomerExceededFault Region/account cluster soft limit hit Raise the quota via Service Quotas
Credential-chain errors on plan/apply No profile/SSO/OIDC resolved, or wrong Region Set AWS_PROFILE / assume the role; confirm the provider Region

πŸ”— Related Docs


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

Releases

Packages

Contributors

Languages