Files
atelier/examples/good/terraform-module.md
T
Jon Chery d1aa5daf2b docs(milestone): complete v0.2 — infrastructure-as-code + kubernetes
---ci---
project: atelier
phase: 5
milestone: v0.2
status: complete
requirements:
  covered: [ATELIER-36, ATELIER-37, ATELIER-38, ATELIER-39, ATELIER-40, ATELIER-41, ATELIER-42, ATELIER-43, ATELIER-44, ATELIER-45, ATELIER-46, ATELIER-47, ATELIER-48, ATELIER-49, ATELIER-50, ATELIER-51, ATELIER-52, ATELIER-53, ATELIER-54, ATELIER-55, ATELIER-56, ATELIER-57, ATELIER-58, ATELIER-59]
  partial: []
---/ci---
2026-08-05 02:20:17 +00:00

4.0 KiB

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

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)

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

variables.tf (P1 Declarative Intent, C2 Clarity)

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)

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.
  • 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.