DEV Community

Cover image for Terraform Modules: A Practical Guide

Posted on

Terraform Modules: A Practical Guide

Among all the concepts that you will need to know about Terraform (providers, states, workspaces, etc.), there are also modules.

Terraform modules are self-contained and reusable collections of infrastructure code, allowing users to encapsulate and share configurations for specific components, promoting ease of maintenance in infrastructure-as-code projects.

They can be used within your company, or shared publicly with the community.

In this article, we will create a module in the simplest way possible by describing the structure and implementing some writing best practices and code validation mechanisms.

Simple example

To illustrate the creation of a module, we will take the common case of S3 buckets to be created on AWS.

The division of the module will depend on your needs. Hashicorp does not recommend writing modules for public resource wrappers, because all these modules are already community contributed.
Here we take a simple case for the demonstration, also if you want to provide a private module according to your company's standards, it may still make sense to make a wrapper.

We will implement the structure explained by Hashicorp, creating only the basic elements that interest us. We also make sure to follow the recommended naming conventions.

Here is the structure that we are going to put in place, I will describe all these elements.

├── .pre-commit-config.yaml
├── examples
│   └──
Enter fullscreen mode Exit fullscreen mode

Main resources defines the main resource we create, in our case, the s3 bucket :

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

resource "aws_s3_bucket_ownership_controls" "this" {
  bucket =
  rule {
    object_ownership = "BucketOwnerPreferred"

resource "aws_s3_bucket_acl" "this" {
  depends_on = [aws_s3_bucket_ownership_controls.this]
  bucket     =
  acl        = "private"
Enter fullscreen mode Exit fullscreen mode defines the variables you can pass to the module :

variable "name" {
  type        = string
  default     = null
  description = "Name for the bucket. If omitted, Terraform will assign a random, unique name."

variable "tags" {
  type        = map(any)
  default     = null
  description = "Details the tags to apply to the bucket"
Enter fullscreen mode Exit fullscreen mode defines the attributes you can export from the module :

output "bucket_id" {
  value       =
  description = "Bucket Name (aka ID)"

output "bucket_arn" {
  value       = aws_s3_bucket.this.arn
  description = "Bucket ARN"

output "bucket_name" {
  value       = aws_s3_bucket.this.bucket
  description = "Bucket Name"
Enter fullscreen mode Exit fullscreen mode

and defines the authorized versions of Terraform and providers to use the module :

terraform {
  required_version = ">=1.2"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.0, < 5.16.0"
Enter fullscreen mode Exit fullscreen mode

Usage example

An example is described in the examples/ file, you can use it to inspire yourself in using the module :

module "test_s3" {
  source = "git::"
  name   = "${var.workspace}-${var.env}-test-bucket"

  tags = {
    Name = "${var.workspace}-${var.env}-test-bucket"
    Env  = var.env
Enter fullscreen mode Exit fullscreen mode


For public repositories, I recommend the GPL (General Public License) v3, it provides strong protections for open-source software, ensuring that any modifications or distributions of the code must also be open source and freely accessible to the community.
If you aren't sure of the license you want to attribute to the project, you can go to


There are two levels of documentation :

  • gives instructions for using the module
  • provides instructions for improving the module itself

Quality validation for contributions

When people contribute to the project, we use a tool called pre-commit, which allows us to improve the code before the changes are shared to the repository.

We use validation scripts, called hooks, notably those from pre-commit-terraform.

.pre-commit-config.yaml defines the hooks we want to execute with pre-commit

- repo:
  rev: v1.85.0
    - id: terraform_fmt
    - id: terraform_tflint
    - id: terraform_checkov
    - id: terraform_docs
Enter fullscreen mode Exit fullscreen mode

Terraform-fmt is a command built into Terraform to ensure that code is formatted correctly.

tflint ensures that Terraform code has no typing or forgetting issues.

checkov avoids committing AWS resources with security vulnerabilities, it analyzes the deployed resources and indicates what does not comply with the rules of security best practices.

terraform-docs generates the well-formatted documentation in the file.

Module exposition

The module can come from a registry or through a local / remote reference.

For local reference, you can call it like this :

module "s3" {
  source = "./path/to/your/module"
Enter fullscreen mode Exit fullscreen mode

For remote reference, you have the choice between https and ssh :

# https
module "s3" {
  source = "git::"# ssh
module "s3" {
  source = ""
Enter fullscreen mode Exit fullscreen mode

You can put the module on the Terraform Registry, but you can very well use it directly without referencing it on this platform.

If you want to reference it to share with the public community, it's quite simple to publish your git repository there. Simply sign up with your Github account, select your repository and click “publish”.

A good practice is to create git tagged releases, in order to point to them during module calls (you can also enter the changelog to follow developments).


The complete code of the demonstration is available on github and on the registry.

You now have all the keys to create and publish a complete and well-configured module, to share within your company or publicly with the community.

Top comments (0)