# Good Example: Terraform Module > A reusable Terraform module that follows Atelier's Infrastructure as Code principles. Each aspect cites the principle it satisfies. ## The Module A versioned module that provisions an S3 bucket with logging, versioning, and encryption — the canonical "secure bucket" pattern, composed rather than copy-pasted. ### Consumer Call ```hcl module "logs_bucket" { source = "registry.example.com/infra/secure-bucket/aws" version = "1.2.0" name = "app-logs" region = "us-east-1" force_destroy = false retention_days = 90 } ``` ### Module Structure ``` secure-bucket/ ├── main.tf # the resource ├── variables.tf # typed inputs ├── outputs.tf # the interface to consumers ├── versions.tf # provider pin └── README.md # the module contract ``` ### `versions.tf` (P5 Version Everything) ```hcl terraform { required_version = ">= 1.5.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } ``` ### `variables.tf` (P1 Declarative Intent, C2 Clarity) ```hcl variable "name" { type = string description = "Globally unique bucket name." validation { condition = can(regex("^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$", var.name)) error_message = "Bucket name must be lowercase, 3-63 chars, DNS-compatible." } } variable "retention_days" { type = number default = 30 description = "S3 lifecycle transition age in days." } ``` ### `main.tf` (P1, P3 State is Truth, P10 Secrets Never in Code) ```hcl resource "aws_s3_bucket" "this" { bucket = var.name } resource "aws_s3_bucket_versioning" "this" { bucket = aws_s3_bucket.this.id versioning_configuration { status = "Enabled" } } resource "aws_s3_bucket_server_side_encryption_configuration" "this" { bucket = aws_s3_bucket.this.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } resource "aws_s3_bucket_lifecycle_configuration" "this" { bucket = aws_s3_bucket.this.id rule { id = "retention" status = "Enabled" filter { prefix = "" } expiration { days = var.retention_days } } } ``` ## What Makes It Good ### Composition (IaC P6 Modules Compose, C6 Composability) - The bucket pattern is one module, versioned once, consumed many times. A new consumer does not copy 40 lines of HCL — they call the module with a `name` and a `retention_days`. - See `domains/infrastructure-as-code/modules.md` for the module-vs-copy boundary. ### Pinning (IaC P5 Version Everything) - The consumer pins `version = "1.2.0"`. The module pins its provider (`version = "~> 5.0"`) and the required Terraform version. A commit is a complete, reproducible world. - No `latest` anywhere. See `domains/infrastructure-as-code/terraform.md`. ### State Discipline (IaC P3 State is Truth, P8 Remote State with Locking) - The consumer's root configuration declares a remote backend with locking (S3 + DynamoDB, GCS, etc.). The module itself does not declare a backend — the consumer owns state. - See `domains/infrastructure-as-code/state.md` for backend selection and locking. ### Secrets Hygiene (IaC P10 Secrets Never in Code) - The bucket is encrypted at rest (SSE-S3 AES256). No secret is hardcoded; encryption is a provider-managed default. If KMS were used, the key would come from a `data` source or a dedicated KMS module — never a literal. - See `domains/security/secrets.md` for the general secrets principles. ### Plan Before Apply (IaC P4 Plan Before Apply) - The consumer runs `terraform plan` before `apply`. The plan shows the new bucket, versioning, encryption, and lifecycle. Every line is reviewed. The plan is the contract review; `apply` is the signature. ### Cross-Domain Links - `domains/devops/P1 Reproducibility` — the module makes the bucket reproducible from source. - `domains/devops/P6 Configuration as Code` — the bucket is config, not a console click. - `domains/security/supply-chain` — a versioned, signed module from a trusted registry is a supply-chain control.