Workshops
Book a 90-minute product workshop led by HashiCorp engineers and product experts during HashiConf Digital Reserve your spot

Organize Configuration with Modules

Use Terraform Modules

In the previous guide, you learned when and why to use Terraform modules. In this guide, you will use modules from the Terraform Registry to provision an example environment on AWS. The concepts you use in this guide will apply to any modules from any source.

Although the concepts in this guide apply to any module, this guide uses Amazon Web Services (AWS) modules. If you would like to follow along with this guide, you will need to follow these steps:

  1. Create an AWS account, or use one you already have.

  2. Configure one of the authentication methods described in our AWS Provider Documentation. The examples in this guide assume that you are using the Shared Credentials file method with the default AWS credentials file and default profile.

  3. Ensure Terraform is installed and available on your command line; if it isn't, download it from terraform.io and install it. If this is your first time installing Terraform, follow our in-depth installation guide.

»Use the Terraform Registry

Open the Terraform Registry page for the VPC module in a new browser tab or window.

Terraform Registry Details Page

You will see information about the module, as well as a link to the source repository. On the right side of the page, you will see a dropdown interface to select the module version, as well as instructions to use the module to provision infrastructure.

When calling a module, the source argument is required. In this example, Terraform will search for a module in the Terraform registry that matches the given string. You could also use a URL or local file path for the source of your modules. See the Terraform documentation for a list of possible module sources.

The other argument shown here is the version. For supported sources, the version will let you define what version or versions of the module will be loaded. In this guide, you will specify an exact version number for the modules you use. You can read about more ways to specify versions in the module documentation.

Other arguments to module blocks are treated as input variables to the modules.

»Create Terraform configuration

In this guide, you will use modules to create an example AWS environment using a Virtual Private Cloud (VPC) and two EC2 instances.

You can follow this guide by manually building the directory structure and files we describe in the guide, or use the following commands to clone this GitHub repo.

Clone the GitHub repository.

$ git clone https://github.com/hashicorp/learn-terraform-modules.git

Change into that directory in your terminal.

$ cd learn-terraform-modules

Check out the ec2-instances tag into a local branch.

$ git checkout tags/ec2-instances -b ec2-instances

For this guide, your main.tf will look like following.

# Terraform configuration

provider "aws" {
  region = "us-west-2"
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "2.21.0"

  name = var.vpc_name
  cidr = var.vpc_cidr

  azs             = var.vpc_azs
  private_subnets = var.vpc_private_subnets
  public_subnets  = var.vpc_public_subnets

  enable_nat_gateway = var.vpc_enable_nat_gateway

  tags = var.vpc_tags
}

module "ec2_instances" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "2.12.0"

  name           = "my-ec2-cluster"
  instance_count = 2

  ami                    = "ami-0c5204531f799e0c6"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [module.vpc.default_security_group_id]
  subnet_id              = module.vpc.public_subnets[0]

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

This configuration includes three blocks:

  • provider "aws" defines your provider. Depending on the authentication method you chose, you may need to include additional arguments in the provider block.
  • module "vpc" defines a Virtual Private Cloud (VPC), which will provide networking services for the rest of your infrastructure.
  • module "ec2_instances" defines two EC2 instances within your VPC.

»Set values for module input variables

In order to use most modules, you will need to pass input variables to the module configuration. The configuration that calls a module is responsible for setting its input values, which are passed as arguments in the module block. Aside from source and version, most of the arguments to a module block will set variable values.

On the Terraform registry page for the AWS VPC module, you will see an Inputs tab that describes all of the input variables that module supports.

Some input variables are required, meaning that the module doesn't provide a default value — an explicit value must be provided in order for Terraform to run correctly.

Within the module "vpc" block, review the input variables you are setting. You can find each of these input variables documented in the Terraform registry

  • name will be the name of the VPC within AWS.
  • cidr describes the CIDR blocks used within your VPC.
  • azs are the availability zones that will be used for the VPC's subnets.
  • private_subnets are subnets within the VPC that will contain resources that do not have a public IP address or route.
  • public_subnets are subnets that will contain resources with public IP addresses and routes.
  • enable_nat_gateway if true, the module will provision NAT gateways for your private subnets.
  • tags specify the tags for each of the resources provisioned by this configuration within AWS.

You can also review the arguments for the module "ec2_instances" block by comparing them to the module documentation.

When creating EC2 instances, you need to specify a subnet and security group for them to use. This example will use the ones provided by the VPC module.

»Define root input variables

Using input variables with modules is very similar to how you use variables in any Terraform configuration. A common pattern is to identify which module input variables you might want to change in the future, and create matching variables in your configuration's variables.tf file with sensible default values. Those variables can then be passed to the module block as arguments.

Not all module input variables need to be set using variables in your configuration. For instance, you might want this VPC to always have a NAT gateway enabled, because the application you are provisioning requires it. In that case, using a variable to set enable_nat_gateway would be counterproductive.

You will need to define these variables in your configuration to use them.

If you have cloned the git repository mentioned earlier in this guide, your variables.tf will look like the below.

Otherwise, copy and paste the following into variables.tf:

# Input variable definitions

variable "vpc_name" {
  description = "Name of VPC"
  type        = string
  default     = "example-vpc"
}

variable "vpc_cidr" {
  description = "CIDR block for VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "vpc_azs" {
  description = "Availability zones for VPC"
  type        = list
  default     = ["us-west-2a", "us-west-2b", "us-west-2c"]
}

variable "vpc_private_subnets" {
  description = "Private subnets for VPC"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "vpc_public_subnets" {
  description = "Public subnets for VPC"
  type        = list(string)
  default     = ["10.0.101.0/24", "10.0.102.0/24"]
}

variable "vpc_enable_nat_gateway" {
  description = "Enable NAT gateway for VPC"
  type    = bool
  default = true
}

variable "vpc_tags" {
  description = "Tags to apply to resources created by VPC module"
  type        = map(string)
  default     = {
    Terraform   = "true"
    Environment = "dev"
  }
}

»Define root output values

Modules also have output values, which are defined within the module with the output keyword. You can access them by referring to module.<MODULE NAME>.<OUTPUT NAME>. Like input variables, module outputs are listed under the outputs tab in the Terraform registry.

Module outputs are usually either passed to other parts of your configuration, or defined as outputs in your root module. You will see both uses in this guide.

Inside your configuration's directory, outputs.tf will need to contain:

output "vpc_public_subnets" {
  description = "IDs of the VPC's public subnets"
  value       = module.vpc.public_subnets
}

output "ec2_instance_public_ips" {
  description = "Public IP addresses of EC2 instances"
  value       = module.ec2_instances.public_ip
}

In this example, the value of the vpc_public_subnets will come from the public_subnets output from the module named vpc, and ec2_instance_public_ips is defined as module.ec2_instances.public_ip.

»Provision infrastructure

Initialize your Terraform configuration by running terraform init.

$ terraform init
Initializing modules...
Downloading terraform-aws-modules/ec2-instance/aws 2.12.0 for ec2_instances...
- ec2_instances in .terraform/modules/ec2_instances/terraform-aws-modules-terraform-aws-ec2-instance-ed6dcd9
Downloading terraform-aws-modules/vpc/aws 2.21.0 for vpc...
- vpc in .terraform/modules/vpc/terraform-aws-modules-terraform-aws-vpc-2417f60

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.44.0...

# ...

Terraform has installed the provider and both of the modules your configuration refers to.

Now run terraform apply to create your VPC and EC2 instances:

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.ec2_instances.aws_instance.this[0] will be created
  + resource "aws_instance" "this" {
      + ami                          = "ami-0c5204531f799e0c6"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)

# ...

Plan: 22 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

You will notice that many more resources than just the VPC and EC2 instances will be created. The modules we used define what those resources are.

Respond to the prompt with yes to apply the changes and continue.

You should see the instance IP addresses in your configuration's output:

# ...

Outputs:

ec2_instance_public_ips = [
  "34.220.43.248",
  "34.208.3.72",
]
vpc_public_subnets = [
  "subnet-0ecd7a5db2a78879d",
  "subnet-005a1cfe9fbbf596c",
]

»Understand how modules work

When using a new module for the first time, you must run either terraform init or terraform get to install the module. When either of these commands are run, Terraform will install any new modules in the .terraform/modules directory within your configuration's working directory. For local modules, Terraform will create a symlink to the module's directory. Because of this, any changes to local modules will be effective immediately, without having to re-run terraform get.

After following this guide, your .terraform/modules directory will look something like this:

.terraform/modules
├── ec2_instances
│   └── terraform-aws-modules-terraform-aws-ec2-instance-ed6dcd9
├── modules.json
└── vpc
    └── terraform-aws-modules-terraform-aws-vpc-2417f60

»Clean up your infrastructure

Now you have seen how to use modules from the Terraform registry, how to configure those modules with input variables, and how to get output values from those modules.

Before moving on to the next guide, destroy the infrastructure you created by running the terraform destroy command:

$ terraform destroy
module.vpc.aws_eip.nat[1]: Refreshing state... [id=eipalloc-019208f8c88f9c7d6]
module.vpc.aws_eip.nat[0]: Refreshing state... [id=eipalloc-0046def26f17a2eb2]
module.vpc.aws_vpc.this[0]: Refreshing state... [id=vpc-0d6e08bf4f27de4ab]

# ...

Plan: 0 to add, 0 to change, 22 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

module.vpc.aws_route_table_association.private[1]: Destroying... [id=rtbassoc-0cc54bc0390c1af5e]
module.vpc.aws_route_table_association.private[0]: Destroying... [id=rtbassoc-0d435e5616f58a9fc]

# ...

module.vpc.aws_internet_gateway.this[0]: Destruction complete after 11s
module.vpc.aws_vpc.this[0]: Destroying... [id=vpc-0d6e08bf4f27de4ab]
module.vpc.aws_vpc.this[0]: Destruction complete after 0s

Destroy complete! Resources: 22 destroyed.

After you respond to the prompt with yes, Terraform will destroy the infrastructure you created.

»Next steps

In this guide, you have learned how to:

  • Use modules in your Terraform configuration
  • Manage module versions
  • Use the Terraform Registry
  • Configure module input variables
  • Use module output values

In the next guide, you will host a website in an S3 bucket by creating and calling a child module from the configuration you built in this guide.