Build your first Terraform module: a secure, reusable S3 bucket

Writing your first Terraform module is a great way to turn repeating infrastructure patterns into a single, well-scoped unit you can reuse, test, and version. For many teams, a secure S3 bucket is one of the first sensible modules: it’s small, useful across projects, and benefits from sane defaults (encryption, versioning, and public-access controls). This article walks through the design and minimal code for a beginner-friendly S3 module and explains the decisions that make it reusable and safe.

Why make a module for S3 buckets?

HashiCorp documents a small set of module conventions (main.tf, variables.tf, outputs.tf, README) and tooling that expects a standard layout — following these conventions makes your module easier to adopt and publish. (developer.hashicorp.com)

What this module will enforce

This example module focuses on a conservative, production-friendly baseline:

These defaults map to current AWS guidance for securing S3: block unintended public access and enforce encryption, versioning, and lifecycle controls for important data. (docs.aws.amazon.com)

Follow the standard structure:

HashiCorp’s tutorials and guidance describe this pattern and common module workflows (create locally, call from root module, iterate and publish). (developer.hashicorp.com)

Minimal example: code snippets

Below are compact, focused snippets that illustrate the core of the module. They intentionally leave room for extension (ACLs, logging, replication).

main.tf

resource "aws_s3_bucket" "this" {
  bucket = var.bucket
  tags   = var.tags
}

resource "aws_s3_bucket_public_access_block" "this" {
  bucket = aws_s3_bucket.this.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_versioning" "this" {
  bucket = aws_s3_bucket.this.id
  versioning_configuration {
    status = var.versioning ? "Enabled" : "Suspended"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
  bucket = aws_s3_bucket.this.id

  dynamic "rule" {
    for_each = var.kms_key_arn != "" ? [1] : []
    content {
      apply_server_side_encryption_by_default {
        kms_master_key_id = var.kms_key_arn
        sse_algorithm     = "aws:kms"
      }
    }
  }

  # Fallback to SSE-S3 if no KMS key provided (provider resource may vary)
}

variables.tf

variable "bucket" {
  type        = string
  description = "Name of the S3 bucket"
}

variable "tags" {
  type    = map(string)
  default = {}
}

variable "versioning" {
  type    = bool
  default = true
}

variable "kms_key_arn" {
  type        = string
  default     = ""
  description = "Optional KMS key ARN to use for SSE-KMS. If empty, use SSE-S3."
}

outputs.tf

output "bucket_id" {
  value = aws_s3_bucket.this.id
  description = "The bucket name / id"
}

output "bucket_arn" {
  value = aws_s3_bucket.this.arn
  description = "ARN of the bucket"
}

This minimal module enforces block public access and versioning and supports optional SSE-KMS. A full production module would add lifecycle rules (to limit costs and keep only necessary historical versions), logging/metrics, and configurable policies for cross-account access.

Community-maintained modules (for example the widely used terraform-aws-modules/s3-bucket) expose many more options and are good references for advanced features like replication, object lock, or ACL controls. Looking at these mature modules helps you see how to evolve your module without re-inventing complex edge cases. (registry.terraform.io)

Design tips for a beginner-friendly module

Common pitfalls

Where to look next

Summary

A first Terraform module should be small, well-documented, and opinionated about safe defaults. The S3 bucket module above provides a compact example that enforces block public access, versioning, and encryption while remaining easy to extend. Following the standard structure (main.tf, variables.tf, outputs.tf, README) keeps the module approachable for others and compatible with Terraform tooling. (developer.hashicorp.com)