A secure-by-default Amazon DocumentDB (MongoDB-compatible) cluster — encrypted at rest, TLS-required in transit, deletion-protected, and audit-logged — with its instances, DB subnet group, and cluster parameter group provisioned from a single composite call. Built for the AWS provider v6.x.
- 🗄️ Provisions an Amazon DocumentDB cluster (
aws_docdb_cluster) — AWS's managed, MongoDB-compatible document database — as the keystone resource. - 👥 Creates writer + reader instances as a
for_eachmap keyed by a stable, caller-supplied name, so each member is independently addressable and resizable without churning its siblings. - 🌐 Owns the DB subnet group (spanning ≥ 2 AZs) and the cluster parameter group (with
tls = enabledbaked in) so a single call yields a complete, private topology. - 🔒 Secure by default: storage encryption on, TLS required, deletion protection on, final snapshot taken, 7-day backups, and
auditlogs exported to CloudWatch. - 🔑 Master credentials are managed in AWS Secrets Manager by default (
manage_master_user_password), so no password is stored in Terraform state. - 🧩 Consumes networking, security, and KMS by reference — it never creates a VPC, security group, or KMS key itself.
💡 Why it matters: DocumentDB clusters routinely hold PII under privacy-regulation. An unencrypted or publicly-reachable document store is a far larger blast radius than the convenience saved by a loose default — so this module ships locked down and makes you opt out explicitly.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
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!
flowchart LR
VPC["terraform-aws-vpc"]
SG["terraform-aws-security-group"]
KMS["terraform-aws-kms"]
SM["terraform-aws-secrets-manager"]
DOC["terraform-aws-documentdb"]
APP["Application tier<br/>(ECS / EKS / EC2)"]
VPC -->|subnet_ids| DOC
SG -->|vpc_security_group_ids| DOC
KMS -->|kms_key_arn| DOC
DOC -->|master_user_secret| SM
DOC -->|endpoint / reader_endpoint| APP
style DOC fill:#FF9900,color:#fff
DocumentDB sits at the data tier. It is downstream of the networking, security-group, and KMS foundations and upstream of the application tier that connects to its endpoints. Secrets Manager holds the managed master password.
flowchart TD
subgraph caller["Caller-supplied (by reference)"]
SUBNETS["subnet_ids<br/>(terraform-aws-vpc)"]
SGS["vpc_security_group_ids<br/>(terraform-aws-security-group)"]
CMK["kms_key_arn<br/>(terraform-aws-kms)"]
end
subgraph mod["terraform-aws-documentdb"]
SG["aws_docdb_subnet_group.this"]
PG["aws_docdb_cluster_parameter_group.this<br/>(tls = enabled)"]
C["aws_docdb_cluster.this<br/>keystone — encrypted, deletion-protected"]
I["aws_docdb_cluster_instance.this<br/>for_each — writer + readers"]
SEC["master_user_secret<br/>(Secrets Manager, managed)"]
end
SUBNETS --> SG
SG --> C
PG --> C
SGS --> C
CMK --> C
C --> I
C -. manages.-> SEC
style C fill:#FF9900,color:#fff
| Resource | Role |
|---|---|
aws_docdb_cluster.this |
Keystone — the cluster control plane, encryption, backups, deletion protection |
aws_docdb_cluster_instance.this |
Writer + reader instances (for_each over var.instances) |
aws_docdb_subnet_group.this |
Placement across caller-supplied subnets (≥ 2 AZs) |
aws_docdb_cluster_parameter_group.this |
Cluster parameters, with tls = enabled by default |
| 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 Region model note below.
DocumentDB shares the RDS control plane, so it is managed through the rds: IAM action namespace — there is no docdb: namespace. The Terraform identity needs (least-privilege):
| Action | Required for | Notes |
|---|---|---|
rds:CreateDBCluster, rds:DeleteDBCluster, rds:ModifyDBCluster, rds:DescribeDBClusters |
Cluster lifecycle | Keystone resource |
rds:CreateDBInstance, rds:DeleteDBInstance, rds:ModifyDBInstance, rds:DescribeDBInstances |
Cluster instances | One per instances map entry |
rds:CreateDBSubnetGroup, rds:DeleteDBSubnetGroup, rds:DescribeDBSubnetGroups |
DB subnet group | Module-owned |
rds:CreateDBClusterParameterGroup, rds:ModifyDBClusterParameterGroup, rds:DeleteDBClusterParameterGroup, rds:DescribeDBClusterParameterGroups |
Cluster parameter group | Carries the tls parameter |
rds:AddTagsToResource, rds:RemoveTagsFromResource, rds:ListTagsForResource |
Tagging | All taggable resources |
rds:CreateDBClusterSnapshot, rds:DescribeDBClusterSnapshots |
Final / manual snapshots | Final snapshot on destroy |
secretsmanager:CreateSecret, secretsmanager:TagResource, secretsmanager:DescribeSecret |
Managed master password | When manage_master_user_password = true (default) |
kms:DescribeKey, kms:CreateGrant, kms:RetireGrant |
CMK storage encryption | Only when kms_key_arn (CMK) is supplied |
iam:CreateServiceLinkedRole |
First-time AWSServiceRoleForRDS creation |
One-time per account; harmless if it already exists |
⚠️ Scope the resource ARNs in your policy toarn:aws:rds:<region>:<account>:cluster:*/:db:*/:subgrp:*/:cluster-pg:*patterns rather than"*"where your governance allows.
- Service-linked role:
AWSServiceRoleForRDS(DocumentDB shares the RDS control plane) is auto-created on first use;iam:CreateServiceLinkedRolecovers the one-time creation. - Networking: the supplied
subnet_idsmust span at least two Availability Zones in the target VPC. DocumentDB internally assigns up to three AZs — if you setavailability_zonesto fewer than 3, the next plan shows a difference that forces recreation, so either supply 3 AZs or leave itnull. - Customer-managed KMS key (optional): to use a CMK for storage encryption, its key policy must allow the RDS/DocumentDB service principal (
rds.amazonaws.com) the standardkms:Encrypt/Decrypt/GenerateDataKey*/CreateGrantset. Switching the key later forces a new cluster. - TLS in transit: the
tlscluster parameter is enabled by default. Clients must present the Amazon DocumentDB CA bundle (global-bundle.pem). A FIPS 140-3 TLS option also exists at the parameter level for compliance-sensitive workloads. - Engine / instance availability: the chosen
engine_version(and itsparameter_group_family, e.g.docdb5.0) and eachinstance_classmust be offered in the target Region — verify withaws docdb describe-db-engine-versions/describe-orderable-db-instance-options. - Region model: the module relies on provider inheritance — there is no
regionvariable. The caller's provider block (or alias) sets the Region. DocumentDB is not a us-east-1 global-service constraint. - Service quotas: default soft limits apply per Region (e.g. clusters and instances per Region, instances per cluster). All are raisable via a Service Quotas request. See Amazon DocumentDB quotas and limits.
terraform-aws-documentdb/
├── 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, cluster (this), instances (for_each)
├── outputs.tf # id + arn, endpoints, secret, instance maps, tags_all
├── SCOPE.md # boundary, IAM, prerequisites, gotchas, secure defaults
└── README.md # this file
The smallest working call — networking, security, and KMS wired from upstream modules:
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-core-docdb"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
# manage_master_user_password defaults to true → password lives in Secrets Manager
instances = {
writer = { instance_class = "db.r6g.large", promotion_tier = 0 }
reader1 = { instance_class = "db.r6g.large", promotion_tier = 1 }
}
tags = {
Environment = "prod"
DataClass = "PII"
CostCenter = "platform-data"
}
}Everything not shown inherits the secure baseline: storage encryption on, TLS required, deletion protection on, final snapshot on destroy, 7-day backups, audit logs exported.
| Input | Type | Source module |
|---|---|---|
subnet_ids |
list(string) |
terraform-aws-vpc (private subnets, ≥ 2 AZs) |
vpc_security_group_ids |
list(string) |
terraform-aws-security-group |
kms_key_arn |
string (KMS key ARN, optional) |
terraform-aws-kms |
master_password |
string (sensitive, optional) |
terraform-aws-secrets-manager (only if self-managing) |
| Output | Description | Consumed by |
|---|---|---|
id |
Cluster identifier (resource id) | references |
arn |
Cluster ARN — cross-resource reference type | IAM policies, AWS Backup, monitoring |
cluster_identifier |
Cluster identifier | CLI / console |
cluster_resource_id |
Immutable, Region-unique resource id | IAM policy conditions, CloudTrail |
endpoint |
Cluster (writer) endpoint | application write config |
reader_endpoint |
Load-balanced reader endpoint | read traffic |
port |
Cluster port (default 27017) | application config |
hosted_zone_id |
Route 53 hosted zone of the endpoint | alias records |
cluster_members |
Instance identifiers in the cluster | monitoring |
master_user_secret |
Managed master-password secret block (ARN, KMS key, status) | terraform-aws-secrets-manager consumers / apps |
master_username |
Master username (sensitive) | app bootstrap |
instance_ids / instance_arns / instance_endpoints |
Per-instance maps keyed by instances key |
monitoring, DNS |
writer_instance_keys |
Map keys currently acting as writer | failover-aware tooling |
subnet_group_name / subnet_group_arn |
DB subnet group | references |
parameter_group_name / parameter_group_arn |
Cluster parameter group | references |
tags_all |
All tags incl. provider default_tags |
governance / audit |
1 · Minimal cluster (managed secret, secure defaults)
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-min"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
instances = {
writer = { instance_class = "db.r6g.large" }
}
}2 · Customer-managed KMS key (CMK) for storage encryption
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-cmk"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
storage_encrypted = true # default
kms_key_arn = module.kms.arn # auditable, independently-revocable CMK
instances = {
writer = { instance_class = "db.r6g.xlarge", promotion_tier = 0 }
reader1 = { instance_class = "db.r6g.xlarge", promotion_tier = 1 }
}
}Switching
kms_key_arnlater forces a new cluster — choose the CMK at creation.
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 "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-tagged"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
instances = { writer = { instance_class = "db.r6g.large" } }
tags = {
Environment = "prod"
DataClass = "PII"
Org = "-Data" # resource tag wins over default_tags on key conflict
}
}
# module.documentdb.tags_all => { ManagedBy, Org=-Data, Environment, DataClass }4 · High-availability multi-AZ topology (writer + 2 readers)
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-ha"
subnet_ids = module.vpc.private_subnet_ids # 3 AZs
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
instances = {
writer = { instance_class = "db.r6g.2xlarge", promotion_tier = 0 }
reader1 = { instance_class = "db.r6g.2xlarge", promotion_tier = 1 }
reader2 = { instance_class = "db.r6g.xlarge", promotion_tier = 2 }
}
}Lower promotion_tier is promoted first on failover. Manage instances by map key — resizing reader2 never touches writer or reader1.
5 · Self-managed master password from Secrets Manager
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-selfsecret"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
manage_master_user_password = false
master_password = module.docdb_secret.secret_string # sensitive
instances = { writer = { instance_class = "db.r6g.large" } }
}Discouraged — a value here lands in plaintext in state. Prefer the managed-secret default (omit
master_password).
6 · Custom backup window + 35-day retention
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-backups"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
backup_retention_period = 35
preferred_backup_window = "04:00-05:00" # UTC
preferred_maintenance_window = "sun:05:30-sun:06:30" # UTC
instances = { writer = { instance_class = "db.r6g.large" } }
}7 · Audit + profiler log exports to CloudWatch
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-logs"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
enabled_cloudwatch_logs_exports = ["audit", "profiler"]
instances = { writer = { instance_class = "db.r6g.large" } }
}Enabling
profileralso requires the matching profiler parameters in the cluster parameter group (see example 8).
8 · Custom cluster parameters (keep TLS, add profiler)
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-params"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
parameter_group_family = "docdb5.0"
cluster_parameters = {
tls = { value = "enabled" } # keep the secure default
profiler = { value = "enabled" }
profiler_threshold_ms = { value = "100" }
audit_logs = { value = "all" }
ttl_monitor = { value = "enabled" }
}
enabled_cloudwatch_logs_exports = ["audit", "profiler"]
instances = { writer = { instance_class = "db.r6g.large" } }
}9 · I/O-Optimized storage
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-iopt"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
storage_type = "iopt1" # predictable price for I/O-heavy workloads
instances = {
writer = { instance_class = "db.r6g.2xlarge" }
reader1 = { instance_class = "db.r6g.2xlarge" }
}
}10 · Performance Insights on instances (with CMK)
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-pi"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin"
kms_key_arn = module.kms.arn
instances = {
writer = {
instance_class = "db.r6g.xlarge"
enable_performance_insights = true
performance_insights_kms_key_id = module.kms.arn
}
reader1 = {
instance_class = "db.r6g.xlarge"
enable_performance_insights = true
}
}
}11 · Restore from snapshot
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-restored"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
snapshot_identifier = "casey-core-docdb-2026-06-18" # FORCE-NEW; master_username/password ignored
instances = { writer = { instance_class = "db.r6g.large" } }
}12 · Point-in-time restore (PITR)
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-pitr"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
restore_to_point_in_time = {
source_cluster_identifier = "casey-core-docdb"
restore_type = "copy-on-write"
use_latest_restorable_time = true
}
instances = { writer = { instance_class = "db.r6g.large" } }
}13 · Disposable dev cluster (secure defaults relaxed)
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-dev"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "devadmin"
# OPT-OUTS — dev only, never in prod / PII:
deletion_protection = false
skip_final_snapshot = true
apply_immediately = true
instances = { writer = { instance_class = "db.t3.medium" } }
tags = { Environment = "dev", DataClass = "synthetic" }
}Storage encryption and TLS remain on — even disposable clusters stay encrypted.
14 · DocumentDB global cluster (secondary region member)
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-docdb-dr"
subnet_ids = module.vpc_dr.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg_dr.id]
global_cluster_identifier = aws_docdb_global_cluster.casey.id # join a global cluster
# master_username omitted on a global secondary
instances = { writer = { instance_class = "db.r6g.xlarge" } }
}15 · 🏁 End-to-end composition (VPC → SG → KMS → DocumentDB → Secrets)
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 DocumentDB storage encryption"
alias = "alias/casey-docdb"
}
module "docdb_sg" {
source = "git::https://github.com/microsoftexpert/terraform-aws-security-group?ref=v1.0.0"
name = "casey-docdb-sg"
vpc_id = module.vpc.id
ingress_rules = {
mongo = {
from_port = 27017
to_port = 27017
ip_protocol = "tcp"
referenced_security_group_id = module.app_sg.id # app tier only
description = "DocumentDB from application tier"
}
}
}
module "documentdb" {
source = "git::https://github.com/microsoftexpert/terraform-aws-documentdb?ref=v1.0.0"
cluster_identifier = "casey-core-docdb"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.docdb_sg.id]
master_username = "caseyadmin" # managed-secret path (default)
kms_key_arn = module.kms.arn
backup_retention_period = 14
enabled_cloudwatch_logs_exports = ["audit"]
instances = {
writer = { instance_class = "db.r6g.xlarge", promotion_tier = 0 }
reader1 = { instance_class = "db.r6g.xlarge", promotion_tier = 1 }
}
tags = {
Environment = "prod"
DataClass = "PII"
Compliance = "privacy-regulation"
}
}
# The application reads the managed password straight from Secrets Manager:
output "docdb_secret_arn" {
value = module.documentdb.master_user_secret[0].secret_arn
}
output "docdb_writer_endpoint" { value = module.documentdb.endpoint }
output "docdb_reader_endpoint" { value = module.documentdb.reader_endpoint }| Name | Type | Default | Description |
|---|---|---|---|
cluster_identifier |
string |
— (required) | Cluster id. FORCE-NEW. lowercase, 1–63 chars |
subnet_ids |
list(string) |
— (required) | DB subnet group members, ≥ 2 AZs |
instances |
map(object) |
— (required) | Writer + reader instances keyed by stable name |
vpc_security_group_ids |
list(string) |
[] |
Security groups to attach |
engine |
string |
"docdb" |
Only docdb valid. FORCE-NEW |
engine_version |
string |
null |
Engine version; null = AWS default |
port |
number |
27017 |
Connection port |
availability_zones |
list(string) |
null |
Explicit AZs (use 3 or leave null) |
master_username |
string |
null |
Master user. FORCE-NEW |
manage_master_user_password |
bool |
true |
Manage password in Secrets Manager (secure default) |
master_password |
string (sensitive) |
null |
Self-managed password (discouraged) |
storage_encrypted |
bool |
true |
Encryption at rest. FORCE-NEW |
kms_key_arn |
string |
null |
CMK for storage encryption. FORCE-NEW |
storage_type |
string |
null |
standard or iopt1 |
deletion_protection |
bool |
true |
Block destroy |
skip_final_snapshot |
bool |
false |
Skip final snapshot on destroy |
final_snapshot_identifier |
string |
null |
Name of final snapshot |
snapshot_identifier |
string |
null |
Restore from snapshot. FORCE-NEW |
backup_retention_period |
number |
7 |
Backup retention days (1–35) |
preferred_backup_window |
string |
null |
UTC backup window |
preferred_maintenance_window |
string |
null |
UTC maintenance window |
enabled_cloudwatch_logs_exports |
list(string) |
["audit"] |
audit and/or profiler |
apply_immediately |
bool |
false |
Apply changes now vs. maintenance window |
allow_major_version_upgrade |
bool |
false |
Permit major upgrades |
global_cluster_identifier |
string |
null |
Join a global cluster |
restore_to_point_in_time |
object |
null |
PITR config. FORCE-NEW |
subnet_group_name / subnet_group_description |
string |
derived | DB subnet group naming |
parameter_group_family |
string |
"docdb5.0" |
Parameter group family. FORCE-NEW |
parameter_group_name / parameter_group_description |
string |
derived | Parameter group naming |
cluster_parameters |
map(object) |
{ tls = enabled } |
Cluster parameters (TLS on by default) |
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.
See the Emits table above. Primary outputs are id and arn; instance-level data is exposed as maps keyed by your instances keys; master_user_secret carries the managed-secret ARN; tags_all reflects the merged tag set.
- ARN / ID formats.
- Cluster
arn:arn:aws:rds:<region>:<account>:cluster:<cluster_identifier> - Instance
arn:arn:aws:rds:<region>:<account>:db:<instance_identifier> cluster_resource_idis a stable, Region-unique id (e.g.cluster-ABC123...) — use it (not the name) in IAM policy conditions and CloudTrail correlation, since it survives rename.rds:namespace. DocumentDB rides the RDS control plane — every API call, ARN service segment, and IAM action usesrds, notdocdb. Write least-privilege policies accordingly.- FORCE-NEW (immutable) fields.
cluster_identifier,engine,master_username,db_subnet_group_name(membership),parameter_group_family,storage_encrypted, switchingkms_key_arn,snapshot_identifier, andrestore_to_point_in_timeall destroy-and-recreate the cluster. Per-instanceavailability_zoneforces replacement of that instance. tags↔tags_all↔default_tags. The module sets only resource-leveltags. The provider'sdefault_tagsis 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 instance creation is asynchronous; the writer is promoted from cluster members by DocumentDB, so
writer_instance_keysmay settle a moment after apply. Endpoints (endpoint,reader_endpoint) are stable DNS names that follow failover. - Three-AZ behavior. DocumentDB auto-distributes across up to 3 AZs. Setting
availability_zonesto fewer than 3 produces a perpetual diff that forces recreation — leave itnullor supply 3. - Destroy ordering. Terraform tears down instances → cluster → subnet/parameter groups. With
deletion_protection = true(default) the cluster destroy is blocked until you set itfalse; withskip_final_snapshot = false(default) a final snapshot is taken first. The DB subnet group cannot be deleted while the cluster still references it — Terraform's dependency graph handles the order, but a half-failed destroy can leave a subnet group pinned by a lingering cluster. - No us-east-1 constraint. DocumentDB is a regional service — none of the us-east-1 global-service rules (CloudFront/WAF/ACM) apply.
Secure by default; every weakening is an explicit, documented opt-out.
| Posture | Default | How to opt out |
|---|---|---|
| Storage encryption at rest | storage_encrypted = true (AWS-managed key) |
storage_encrypted = false (discouraged) |
| Customer-managed key | available via kms_key_arn |
omit for the AWS-managed key |
| In-transit TLS | tls cluster parameter enabled |
set cluster_parameters.tls = "disabled" (strongly discouraged) |
| Public exposure | private subnets only — no public option | n/a |
| Deletion protection | deletion_protection = true |
deletion_protection = false |
| Final snapshot | skip_final_snapshot = false |
skip_final_snapshot = true (discouraged) |
| Backups | backup_retention_period = 7 |
lower to a minimum of 1 |
| Audit logging | enabled_cloudwatch_logs_exports = ["audit"] |
set [] (discouraged) |
| Managed master password | manage_master_user_password = true (Secrets Manager) |
supply master_password (plaintext-in-state, discouraged) |
Other principles: exactly four .tf files; single keystone aws_docdb_cluster.this; child instances via for_each over map(object) (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.
# 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/applyrequire a valid credential chain (profile / SSO / OIDC web-identity) and a configured Region. The module declares noprovider {}block — supply it (and anyassume_role) at the root.
terraform init -backend=false && terraform validate— schema and reference integrity.terraform fmt -check— canonical formatting.terraform planagainst a sandbox account — confirm secure defaults render (encryption, TLS parameter, deletion protection, audit export) and thatinstancesproduce one writer + N readers.- Post-apply smoke test: connect with the DocumentDB CA bundle over TLS and verify a
serverStatus/isMasteragainst the writer endpoint.
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
arn = "arn:aws:rds:us-east-2:123456789012:cluster:casey-core-docdb"
docdb_reader_endpoint = "casey-core-docdb.cluster-ro-abc123.us-east-2.docdb.amazonaws.com"
docdb_secret_arn = "arn:aws:secretsmanager:us-east-2:123456789012:secret:rds!cluster-...-AbC123"
docdb_writer_endpoint = "casey-core-docdb.cluster-abc123.us-east-2.docdb.amazonaws.com"
id = "casey-core-docdb"
writer_instance_keys = ["writer"]
| 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 rds:CreateDBCluster |
Identity lacks the rds: actions (DocumentDB uses rds, not docdb) |
Attach the Required IAM Permissions |
Cannot delete cluster... deletion protection |
deletion_protection = true (default) |
Set deletion_protection = false, apply, then destroy |
| Destroy hangs on final snapshot | skip_final_snapshot = false (default) needs a name |
Set final_snapshot_identifier, or skip_final_snapshot = true for disposable clusters |
Perpetual diff on availability_zones |
Fewer than 3 AZs specified | Supply 3 AZs or leave null (DocumentDB auto-assigns) |
| Clients can't connect / TLS handshake fails | tls enabled but client missing CA bundle |
Use the Amazon DocumentDB CA bundle (global-bundle.pem) |
InvalidParameterCombination switching kms_key_arn |
KMS key is FORCE-NEW; can't change in place | Recreate the cluster (or restore from snapshot under a new key) |
| Subnet group won't delete | A lingering cluster still references it (half-failed destroy) | Remove the cluster first; Terraform's graph normally orders this |
DBClusterQuotaExceeded |
Region soft limit on clusters/instances 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 |
- Amazon DocumentDB Developer Guide
- Amazon DocumentDB quotas and limits
- Encrypting data in transit (TLS)
- Creating an Amazon DocumentDB cluster
- Terraform
aws_docdb_cluster - Module
SCOPE.md— boundary, IAM, prerequisites, gotchas - Upstream:
terraform-aws-vpc,terraform-aws-security-group,terraform-aws-kms,terraform-aws-secrets-manager
🧡 "Infrastructure as Code should be standardized, consistent, and secure."