A secure-by-default Amazon Redshift data warehouse in one of two selectable shapes — a provisioned cluster or a Redshift Serverless namespace + workgroup — encrypted, private, and logging-enabled out of the box. Built for the AWS provider v6.x.
- 🏢 Provisions a complete Amazon Redshift warehouse from a single module call — choose the shape with one variable,
deployment_mode. 🅰️ provisionedmode builds anaws_redshift_clusterplus its subnet group, an optional parameter group, and optional audit logging.🅱️ serverlessmode builds anaws_redshiftserverless_namespace+aws_redshiftserverless_workgroupwith RPU-based compute.- 🔒 Encrypted at rest by default (AWS-managed KMS, or a customer-managed CMK via
kms_key_arn). - 🚫 Private by default —
publicly_accessible = false, enhanced VPC routing on, placed only in caller-supplied subnets. - 🔑 No plaintext admin password — Secrets Manager generates and manages it by default (
manage_admin_password = true). - 🧾 Audit logging to an S3 bucket or CloudWatch Logs, plus automated snapshots and a final snapshot on destroy.
- 🏷️ Universal tagging — every taggable resource carries
var.tags; the mergedtags_allis surfaced as an output.
💡 Why it matters: a Redshift warehouse often holds the most concentrated PII in an regulatory-regulated estate; this module makes the encrypted, private, logged posture the default and forces an explicit, documented opt-out to weaken it.
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<br/>subnet_ids"]
sg["terraform-aws-security-group<br/>vpc_security_group_ids"]
kms["terraform-aws-kms<br/>kms_key_arn"]
iam["terraform-aws-iam-role<br/>iam_roles (COPY/UNLOAD)"]
s3["terraform-aws-s3-bucket<br/>audit-log bucket"]
sm["terraform-aws-secrets-manager<br/>admin password"]
rs["terraform-aws-redshift"]
bi["BI / app clients<br/>endpoint:port"]
vpc --> rs
sg --> rs
kms --> rs
iam --> rs
s3 --> rs
sm -.-> rs
rs --> bi
style rs fill:#FF9900,color:#fff
The warehouse sits downstream of the networking, encryption, and identity foundations: it consumes subnet_ids from terraform-aws-vpc, vpc_security_group_ids from terraform-aws-security-group, a CMK ARN from terraform-aws-kms, COPY/UNLOAD role ARNs from terraform-aws-iam-role, and an audit-log bucket from terraform-aws-s3-bucket. Admin credentials flow back out to Secrets Manager rather than into a Terraform variable.
flowchart TB
subgraph mod["terraform-aws-redshift (deployment_mode selector)"]
direction TB
subgraph prov["provisioned mode"]
sng["aws_redshift_subnet_group.this"]
pg["aws_redshift_parameter_group.this (optional)"]
cl["aws_redshift_cluster.this — KEYSTONE A"]
log["aws_redshift_logging.this (optional audit log)"]
sng --> cl
pg --> cl
cl --> log
end
subgraph srv["serverless mode"]
ns["aws_redshiftserverless_namespace.this — KEYSTONE B"]
wg["aws_redshiftserverless_workgroup.this"]
ns --> wg
end
end
style cl fill:#FF9900,color:#fff
style ns fill:#FF9900,color:#fff
The two modes are mutually exclusive, gated by var.deployment_mode. Each resource is created via for_each over a single-key map so that only the resources for the selected mode are instantiated; the other mode renders zero resources.
| Resource | Mode | Role |
|---|---|---|
aws_redshift_cluster.this |
provisioned | Keystone A — the cluster |
aws_redshift_subnet_group.this |
provisioned | Placement across caller subnets |
aws_redshift_parameter_group.this |
provisioned | Cluster parameters (only when parameter_group is set) |
aws_redshift_logging.this |
provisioned | Audit logging (only when logging is set) |
aws_redshiftserverless_namespace.this |
serverless | Keystone B — database + admin + encryption |
aws_redshiftserverless_workgroup.this |
serverless | Compute (RPU) + networking |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
ℹ️ This module declares only
required_providers— it contains noprovider {}block. Region and credentials are inherited from the caller's provider configuration.
The Terraform identity needs the following least-privilege actions. Provisioned-only and serverless-only actions are marked; grant only those for the mode you deploy.
| Action | Required for | Notes |
|---|---|---|
redshift:CreateCluster, redshift:DeleteCluster, redshift:ModifyCluster, redshift:DescribeClusters |
Provisioned cluster lifecycle | provisioned mode |
redshift:CreateClusterSubnetGroup, redshift:DeleteClusterSubnetGroup, redshift:ModifyClusterSubnetGroup, redshift:DescribeClusterSubnetGroups |
Subnet group | provisioned mode |
redshift:CreateClusterParameterGroup, redshift:DeleteClusterParameterGroup, redshift:ModifyClusterParameterGroup, redshift:DescribeClusterParameterGroups |
Parameter group | only when parameter_group is set |
redshift:EnableLogging, redshift:DisableLogging, redshift:DescribeLoggingStatus |
Audit logging | only when logging is set |
redshift-serverless:CreateNamespace, redshift-serverless:DeleteNamespace, redshift-serverless:UpdateNamespace, redshift-serverless:GetNamespace |
Serverless namespace | serverless mode |
redshift-serverless:CreateWorkgroup, redshift-serverless:DeleteWorkgroup, redshift-serverless:UpdateWorkgroup, redshift-serverless:GetWorkgroup |
Serverless workgroup | serverless mode |
redshift:CreateTags, redshift:DeleteTags, redshift:DescribeTags |
Tagging | resource-level tags |
iam:PassRole |
Attach COPY/UNLOAD roles to the cluster/namespace | scope to the specific role ARNs in iam_roles; the role must trust redshift.amazonaws.com |
iam:CreateServiceLinkedRole |
Auto-create AWSServiceRoleForRedshift |
only on first use in the account; harmless if it already exists |
kms:DescribeKey, kms:CreateGrant, kms:RetireGrant |
CMK-encrypted warehouse | only when kms_key_arn (or admin_password_secret_kms_key_id) is supplied; the key policy must also allow Redshift / Secrets Manager |
secretsmanager:CreateSecret, secretsmanager:TagResource |
Managed admin password | only when manage_admin_password = true; the secret is created by the Redshift service |
s3:PutObject, s3:GetBucketAcl |
S3 audit-log delivery | granted to the log-delivery service on the bucket policy, not on the Terraform identity |
ec2:DescribeSubnets, ec2:DescribeSecurityGroups, ec2:DescribeVpcs |
Resolve networking inputs | read-only |
ℹ️ The COPY/UNLOAD roles themselves are created by
terraform-aws-iam-roleand only referenced here by ARN.iam:PassRoleis what lets Terraform attach them.
- Service-linked role:
AWSServiceRoleForRedshiftis used by the service to manage ENIs in your VPC. AWS auto-creates it on first cluster/workgroup launch (requiresiam:CreateServiceLinkedRole). - Subnet group (provisioned): the cluster requires a subnet group spanning the target AZs — this module creates it from
subnet_ids. Place those subnets in private address space. - Customer-managed KMS (optional): when supplying
kms_key_arn, the CMK's key policy must grant the Redshift service (and, for the managed-password secret, Secrets Manager) the necessarykms:*actions. - Audit-logging bucket: the S3 bucket named in
logging.bucket_namemust grant the Redshift log-delivery service principals3:PutObjectands3:GetBucketAclvia its bucket policy. Buckets in opt-in Regions have additional account requirements. - COPY/UNLOAD roles: any role in
iam_rolesmust have a trust policy allowingredshift.amazonaws.comto assume it. - Node type / capacity availability: the provisioned
node_type(e.g.ra3.xlplus) or the Serverless base RPU must be offered in the target Region/AZ.multi_azandavailability_zone_relocation_enabledrequire the RA3 family. - Region: Redshift is a regional service — no
us-east-1global constraint applies. Region is inherited from the caller's provider. - Quotas: default soft limits cap clusters and nodes per Region (raisable via Service Quotas). Serverless has default RPU and namespace/workgroup limits per Region.
terraform-aws-redshift/
├── providers.tf # required_providers (aws >= 6.0, < 7.0); no provider block
├── variables.tf # deeply-typed inputs, secure defaults, validation
├── main.tf # subnet/parameter group, cluster, logging, namespace, workgroup
├── outputs.tf # id + arn + endpoint + tags_all + mode-specific outputs
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM, prerequisites, gotchas
A minimal provisioned warehouse, wired from upstream foundation modules:
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-analytics"
node_type = "ra3.xlplus"
# networking (private subnets only)
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
# encryption with a customer-managed CMK
kms_key_arn = module.kms.arn
tags = {
Environment = "prod"
DataClass = "PII"
}
}Encryption (
encrypted = true), private access (publicly_accessible = false), enhanced VPC routing, a Secrets Manager-managed admin password, automated snapshots, and a final snapshot on destroy are all on by default — the call above is already hardened.
| Input | Type | Source module |
|---|---|---|
subnet_ids |
list(string) |
terraform-aws-vpc |
vpc_security_group_ids |
list(string) |
terraform-aws-security-group |
kms_key_arn |
string (KMS key ARN) |
terraform-aws-kms |
admin_password_secret_kms_key_id |
string (KMS key id) |
terraform-aws-kms |
iam_roles / default_iam_role_arn |
list(string) / string (role ARNs) |
terraform-aws-iam-role |
logging.bucket_name |
string (S3 bucket id) |
terraform-aws-s3-bucket |
master_password |
string (sensitive) |
terraform-aws-secrets-manager (only if manage_admin_password = false) |
| Output | Description | Consumed by |
|---|---|---|
id |
Cluster identifier (provisioned) or namespace id (serverless) | references / CLI |
arn |
Cluster ARN (provisioned) or namespace ARN (serverless) — cross-resource reference type | IAM policies, monitoring, KMS grants |
endpoint |
Connection host (provisioned) or workgroup endpoint address (serverless) | BI tools, application config |
port |
Warehouse port (default 5439) | application config, security-group rules |
database_name |
Initial database name | client connection strings |
master_password_secret_arn |
Secrets Manager secret ARN for the admin password (when managed) | app secret retrieval |
namespace_arn / workgroup_arn |
Serverless ARNs | references |
tags_all |
Merged tags incl. provider default_tags |
governance / audit |
1 · Minimal provisioned cluster
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-analytics"
node_type = "ra3.xlplus"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
}2 · Multi-node provisioned cluster
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-warehouse"
node_type = "ra3.4xlarge"
number_of_nodes = 4 # >1 derives cluster_type = "multi-node"
database_name = "analytics"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
}3 · Customer-managed KMS (CMK) encryption
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-secure"
node_type = "ra3.xlplus"
encrypted = true # default
kms_key_arn = module.kms.arn # CMK from terraform-aws-kms; key policy must allow Redshift
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
}4 · Tags (merge with provider default_tags)
# Provider default_tags set by the caller, e.g.:
# provider "aws" { default_tags { tags = { Owner = "platform", CostCenter = "1234" } } }
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-tagged"
node_type = "ra3.xlplus"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
tags = {
Environment = "prod"
DataClass = "PII"
Owner = "data-eng" # resource tag WINS over default_tags Owner = "platform"
}
}
# module.warehouse.tags_all => Owner=data-eng, CostCenter=1234, Environment=prod, DataClass=PII5 · Caller-supplied admin password from Secrets Manager (opt-out of managed password)
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-byo-pw"
node_type = "ra3.xlplus"
manage_admin_password = false # opt out of Secrets Manager management
master_username = "caseyadmin"
master_password = data.aws_secretsmanager_secret_version.redshift.secret_string
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
}Prefer the default
manage_admin_password = true— it keeps the password out of state and plan output entirely.
6 · Audit logging to an S3 bucket
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-logged"
node_type = "ra3.xlplus"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
logging = {
log_destination_type = "s3"
bucket_name = module.audit_logs.id # bucket policy must allow the Redshift log-delivery service
s3_key_prefix = "redshift/casey-logged/"
}
}7 · Audit logging to CloudWatch Logs
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-cw-logged"
node_type = "ra3.xlplus"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "useractivitylog", "userlog"]
}
}8 · Dedicated parameter group with require_ssl
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-tls"
node_type = "ra3.xlplus"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
parameter_group = {
family = "redshift-1.0"
parameters = {
require_ssl = "true"
enable_user_activity_logging = "true"
}
}
}9 · COPY/UNLOAD IAM roles
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-etl"
node_type = "ra3.xlplus"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
iam_roles = [module.copy_role.arn, module.unload_role.arn] # must trust redshift.amazonaws.com
default_iam_role_arn = module.copy_role.arn
}10 · RA3 Multi-AZ high availability
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-ha"
node_type = "ra3.4xlarge"
number_of_nodes = 2
multi_az = true # RA3 only
subnet_ids = module.vpc.private_subnet_ids # span >= 2 AZs
vpc_security_group_ids = [module.redshift_sg.id]
}11 · Restore a provisioned cluster from a snapshot
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-restored"
node_type = "ra3.xlplus"
snapshot_identifier = "casey-analytics-2026-06-20"
# owner_account = "111122223333" # only if restoring a snapshot you do not own
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
}12 · Redshift Serverless namespace + workgroup
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-serverless"
deployment_mode = "serverless"
base_capacity = 32 # RPUs, multiples of 8
max_capacity = 256 # cap burst cost
database_name = "analytics"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
kms_key_arn = module.kms.arn
serverless_log_exports = ["connectionlog", "useractivitylog", "userlog"]
}13 · Serverless with AI price-performance target
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-autoscale"
deployment_mode = "serverless"
base_capacity = 16
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
price_performance_target = {
enabled = true
level = 50 # 1 = favor cost, 100 = favor speed
}
config_parameters = {
require_ssl = "true"
}
}14 · ⚠️ Secure-by-default opt-out (documented exception)
# Use ONLY with a documented, risk-accepted exception. Each line below WEAKENS the baseline.
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-lab"
node_type = "dc2.large"
encrypted = false # ⚠️ at-rest encryption OFF — never for PII
publicly_accessible = true # ⚠️ public endpoint + Elastic IP
enhanced_vpc_routing = false # ⚠️ COPY/UNLOAD can traverse the public internet
skip_final_snapshot = true # ⚠️ data permanently discarded on destroy
subnet_ids = module.vpc.public_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
}15 · 🏁 End-to-end composition (foundations → warehouse)
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
name = "casey-redshift"
}
module "vpc" {
source = "git::https://github.com/microsoftexpert/terraform-aws-vpc?ref=v1.0.0"
name = "casey-data"
#...cidr, subnets...
}
module "redshift_sg" {
source = "git::https://github.com/microsoftexpert/terraform-aws-security-group?ref=v1.0.0"
name = "casey-redshift"
vpc_id = module.vpc.id
ingress_rules = {
redshift = {
from_port = 5439
to_port = 5439
protocol = "tcp"
source_security_group_id = module.app_sg.id # never 0.0.0.0/0
}
}
}
module "audit_logs" {
source = "git::https://github.com/microsoftexpert/terraform-aws-s3-bucket?ref=v1.0.0"
name = "casey-redshift-audit-logs"
#...attach a bucket policy allowing the Redshift log-delivery service...
}
module "copy_role" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
name = "casey-redshift-copy"
assume_role_policy = data.aws_iam_policy_document.redshift_trust.json # trusts redshift.amazonaws.com
}
module "warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-aws-redshift?ref=v1.0.0"
name = "casey-analytics"
node_type = "ra3.4xlarge"
subnet_ids = module.vpc.private_subnet_ids
vpc_security_group_ids = [module.redshift_sg.id]
kms_key_arn = module.kms.arn
iam_roles = [module.copy_role.arn]
default_iam_role_arn = module.copy_role.arn
logging = {
log_destination_type = "s3"
bucket_name = module.audit_logs.id
s3_key_prefix = "redshift/casey-analytics/"
}
tags = {
Environment = "prod"
DataClass = "PII"
}
}
output "warehouse_endpoint" {
value = module.warehouse.endpoint
}- Core / identity:
name,deployment_mode. - Networking:
subnet_ids,vpc_security_group_ids,subnet_group_description,port,enhanced_vpc_routing,publicly_accessible,elastic_ip. - Provisioned compute:
node_type,number_of_nodes,cluster_type,cluster_version,database_name,availability_zone,availability_zone_relocation_enabled,multi_az. - Admin credentials:
master_username,manage_admin_password,master_password(sensitive),admin_password_secret_kms_key_id. - Encryption:
encrypted,kms_key_arn. - IAM roles:
iam_roles,default_iam_role_arn. - Maintenance:
allow_version_upgrade,apply_immediately,preferred_maintenance_window,maintenance_track_name. - Snapshots & recovery:
automated_snapshot_retention_period,manual_snapshot_retention_period,skip_final_snapshot,final_snapshot_identifier,snapshot_identifier,snapshot_arn,snapshot_cluster_identifier,owner_account. - Parameter group / logging (provisioned):
parameter_group_name,parameter_group,logging. - Serverless:
namespace_name,serverless_log_exports,workgroup_name,base_capacity,max_capacity,track_name,config_parameters,price_performance_target. - Universal:
tags,timeouts.
- Primary:
id,arn,name,deployment_mode,tags_all. - Connectivity:
endpoint,port,database_name,dns_name(provisioned only —nullin serverless). - Provisioned cluster:
cluster_identifier,cluster_namespace_arn,cluster_revision_number(allnullin serverless). - Admin secret:
master_password_secret_arn—nullunlessmanage_admin_password = true. - Serverless:
namespace_arn,namespace_id,namespace_name,workgroup_arn,workgroup_id,workgroup_name(allnullin provisioned). - Subnet/parameter groups:
subnet_group_name,subnet_group_arn,parameter_group_name,parameter_group_arn.
Conditionally-present outputs use
try(..., null)and resolve tonullin the mode that does not create the resource. No secret value is emitted — only the Secrets Manager secret ARN.
- ARN / ID formats:
- Provisioned cluster ARN —
arn:aws:redshift:<region>:<account>:cluster:<cluster_identifier>;idis the cluster identifier. - Serverless namespace ARN —
arn:aws:redshift-serverless:<region>:<account>:namespace/<uuid>; workgroup ARN —arn:aws:redshift-serverless:<region>:<account>:workgroup/<uuid>.idis the namespace id. arnis the cross-resource reference type — IAM resource policies, KMS grant principals, and monitoring all key on ARNs.- FORCE-NEW / immutable fields:
cluster_identifier(name),database_name,master_username,cluster_subnet_group_name, and the parameter groupfamilyrecreate the resource on change. Enabling/disablingencryptedor changingkms_key_idtriggers a recreate or a long encryption migration. Changingdeployment_modedestroys one resource set and creates the other — never an in-place edit. - Resize vs recreate: changing
node_typeornumber_of_nodesis a Redshift resize operation, not a destroy — but it can be disruptive and lengthy. tags↔tags_all↔default_tags:var.tagsflows to every taggable resource.tags_allis the computed merge of resource tags over providerdefault_tags, with resource tags winning on key conflict.default_tagsis the caller's provider-block concern and is never set inside this module.- Eventual consistency: a newly created subnet group / parameter group / IAM role may not be immediately visible to the cluster API; the resource graph orders creation, but transient
InvalidParameterValueon first apply can occur and succeed on retry. - Destroy ordering: Redshift manages ENIs in your subnets via the service-linked role; these are torn down when the cluster/workgroup is deleted. If the cluster's security group or subnets are managed in the same root and destroyed concurrently, deletion can stall on lingering ENIs — let Terraform's dependency graph delete the warehouse first. A
skip_final_snapshot = falsecluster also takes a final snapshot on destroy, which extends destroy time. - No
us-east-1global constraint: Redshift is regional; nothing here must live inus-east-1.
Secure-by-default posture, with the variable that relaxes each control:
| Hardened default | Variable | Opt-out |
|---|---|---|
Encryption at rest on (AWS-managed KMS; CMK via kms_key_arn) |
encrypted |
encrypted = false (discouraged; never for PII) |
| Private access (no public endpoint) | publicly_accessible |
publicly_accessible = true (documented exception) |
| Enhanced VPC routing on | enhanced_vpc_routing |
enhanced_vpc_routing = false |
| Admin password in Secrets Manager (never in state) | manage_admin_password |
manage_admin_password = false + master_password |
| Final snapshot taken on destroy | skip_final_snapshot |
skip_final_snapshot = true (discouraged) |
| 7-day automated snapshot retention | automated_snapshot_retention_period |
set to 0 to disable (discouraged) |
| Audit logging available | logging / serverless_log_exports |
leave null/empty to disable |
Additional principles: placement in private subnets only; security groups must restrict ingress to the warehouse port from application SGs (never 0.0.0.0/0); one composite covers both deployment shapes behind a single selector; COPY/UNLOAD roles and the log bucket are referenced by ARN/name to keep the module's blast radius to warehouse resources.
terraform init -backend=false
terraform validate
terraform fmt -check
# plan/apply require valid AWS credentials (profile / SSO / OIDC) and a Region
terraform plan
terraform apply
terraform output
⚠️ Always pin the module source with?ref=v1.0.0— never track a branch.
terraform init -backend=falsethenterraform validate— structural validation, no credentials needed.terraform fmt -check— formatting gate.terraform planagainst a sandbox account to confirm the selecteddeployment_moderenders the expected resource set and that secure defaults are present in the plan.- Verify
tags_allreflects the merge ofvar.tagsover providerdefault_tags. - Confirm
master_passwordnever appears in plan output (it issensitive) and that no password is emitted as an output.
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
arn = "arn:aws:redshift:us-east-1:111122223333:cluster:casey-analytics"
database_name = "analytics"
endpoint = "casey-analytics.abc123.us-east-1.redshift.amazonaws.com:5439"
id = "casey-analytics"
master_password_secret_arn = "arn:aws:secretsmanager:us-east-1:111122223333:secret:redshift!casey-analytics-AbCdEf"
port = 5439
tags_all = tomap({ "DataClass" = "PII", "Environment" = "prod" })
- Tag drift / perpetual diff on tags. A key set in both
var.tagsand providerdefault_tagsshows churn if expectations differ — resource tags win intags_all. Align the two or move the key to one place. Use providerignore_tagsfor tags applied out-of-band. - Credential-chain failures (
NoCredentialProviders,ExpiredToken).validate/fmtwork offline;plan/applyneed a resolved provider — setAWS_PROFILE, refresh SSO, or confirm the OIDC role assumption in CI. InvalidParameterValue/ role-not-found on first apply. Eventual consistency between a just-created IAM role / subnet group and the cluster API — re-run apply; it typically succeeds on retry.AccessDeniedcreating the cluster. Check the IAM table above — common gaps areiam:PassRoleforiam_roles,iam:CreateServiceLinkedRoleforAWSServiceRoleForRedshift, andkms:CreateGrantwhen a CMK is supplied.- Audit logging fails to enable. The S3 bucket policy must grant the Redshift log-delivery service
s3:PutObjectands3:GetBucketAcl; a bucket in a different Region or account, or one with Block Public Access misconfigured for the service principal, will reject delivery. - Destroy stalls on ENIs / NAT / dependencies. Redshift ENIs are removed when the warehouse is deleted; don't destroy the warehouse's subnets/security group in the same operation ahead of it — let the dependency graph delete the cluster first.
- Destroy blocked by missing final-snapshot name. With
skip_final_snapshot = false, afinal_snapshot_identifieris required; the module derives<name>-final, but a name collision with an existing snapshot will fail — supply a unique identifier. - Switching
deployment_moderecreates everything. This is expected — the modes are mutually exclusive. Plan for data migration (snapshot/restore or UNLOAD/COPY) before flipping the selector. - Public-access surprise. If the warehouse is unexpectedly reachable, confirm
publicly_accessible = false(default) and that subnets are private with no0.0.0.0/0ingress on the security group.
- Terraform Registry —
aws_redshift_cluster,aws_redshift_subnet_group,aws_redshift_parameter_group,aws_redshift_logging - Terraform Registry —
aws_redshiftserverless_namespace,aws_redshiftserverless_workgroup - Amazon Redshift Management Guide — provisioned clusters overview, database encryption, database audit logging
- Amazon Redshift — comparing Redshift Serverless to a provisioned data warehouse
- Amazon Redshift — security overview and IAM access-control overview
- Sibling modules —
terraform-aws-vpc,terraform-aws-security-group,terraform-aws-kms,terraform-aws-iam-role,terraform-aws-s3-bucket,terraform-aws-secrets-manager
🧡 "Infrastructure as Code should be standardized, consistent, and secure."