Module Syntax

In Terraform, a module is a reusable block of code that defines a set of related infrastructure resources, such as AWS VPCs, subnets, and instances. The module is a way to organize and structure your Terraform configurations for better manageability and reusability.
In order to call the child module from the root module, a child module will need to follow a specific syntax to set up this call. A module block takes the following syntax:
module "module_name" {
    source = "./path-to-module"
    version = "1.0.0"
    # variables 
}
Here's how it works:
Let's consider an example with two files: ./module/vpc/main.tf (child module) and the main.tf file which is the root module.
// File: ./module/vpc/main.tf (Child Module)
resource "aws_vpc" "main" {
    cidr_block = "10.0.0.0/16"
    instance_tenancy = "default"
}
resource "aws_subnet" "subnet" {
    vpc_id = aws_vpc.main.id
    cidr_block = "10.0.1.0/24"
}
output "subnet_id" {
    value = aws_subnet.subnet.id
}
This child module creates an AWS VPC and a subnet and exports the subnet ID as an output.
// File: main.tf (Root Module)
module "vpc" {
    source = "./modules/vpc"
}
resource "aws_instance" "instance" {
    ami = "ami-abc123456790"
    instance_type = "t2.micro"
    subnet_id = module.vpc.subnet_id
}
In the root module, we import the child module named vpc using the module block. We then specify the source to be ./modules/vpc, a directory where our child module will reside. Then, in the root module, we use the subnet_id output of the child module to create an AWS instance.

Arguments of Module

The following arguments are supported:

1. source

The source is a required argument in a Terraform module block. It informs Terraform about the location from which to get the source code of a child module. When you run the terraform init command, Terraform looks for the source code of the child module at the specified location. If the source code can be found, Terraform downloads it into a .terraform/modules directory on your local machine. The value for the source argument must be a literal string, no template sequences are allowed. No arbitrary expressions are allowed.
The Terraform module installer supports installation from several source types including:

1. Local paths

Local paths is the file path on your local machine that shows the location of the source code for the module. If you set the source argument to a local path, Terraform will look for the source code of the module at that location on your machine. To define a Local Path, you will have to use a relative path which should start with either ./ or ../. That would tell Terraform that this is a local module, not a remote one stored in some registry.
Local Paths are special because they don't need to be "installed" like remote modules do. Since the module is already present on the local disk, Terraform can use it directly. Additionally, if the parent module gets updated, the local module will get updated automatically as well.
For example, suppose you have a Terraform module, say ec2_module residing two levels up from the current directory in a directory called modules/ec2-configuration. You could specify the local path as follows:
module "ec2_module" {
    source = "../../modules/ec2-configuration"
}
Here Terraform will look for the source code of ec2_module two level up from the current directory into modules/ec2configuration directory.
Absolute filesystem paths (e.g /Users/username/modules/ec2-configuration or C:\modules\ec2-configuration) aren't treated like Local Paths by Terraform. Instead of having them as local modules, Terraform will copy the module into the local module cache, as would happen if this was a remote module. This can result in tightly coupling your configuration with the filesystem layout of a particular computer which is something you want to avoid.

2. Terraform Registry

The Terraform Registry is the central location where you can find and share reusable pieces of Terraform configuration, known as modules. These make managing infrastructure easier by providing pre-built code for common infrastructure tasks. There are two types of registries:
The public Terraform Registry is a free community-driven registry where everyone shares and accesses modules. It is like a public library to which everybody can contribute and use. On the other hand, a private registry can be a custom registry hosted internally within an organization. It's like a private library where only authorized users can share or access modules. This kind of registry is able to perform the best for that organization which wants to keep its module inside and accessible only for their own teams.
To use a module from the registry, you will need to specify the source address of the module in your Terraform code. The source is formatted as follows:
[<HOSTNAME>/]<NAMESPACE>/<MODULE_NAME>/<PROVIDER_NAME>
Here,
If you don't specify the HOSTNAME, it goes by default to the public registry managed by HashiCorp. For Example:
module "vpc" {
    source = "terraform-aws-modules/vpc/aws"
    version = "5.8.1"
}
Here, we are using a module from the public registry. The name of this module is "vpc" and the source is in the namespace "terraform-aws-modules". If you want to use a module from a private registry, the source address needs to be prefixed with the hostname of a private registry. For Example:
module "k8s-cluster" {
    source = "app.terraform.io/example-corp/k8s-cluster/provider-name"
    version = "0.0.1"
}
Here we are using a module from a private registry hosted at app.terraform.io. The source name of the module is "k8s-cluster", from namespace "example-corp". We are using version 0.0.1 of said module.

3. Generic Git Repository

In Terraform, you can use any arbitrary Git repository as a module source, just by prefixing the repository URL with git::. This allows you to specify any valid Git URL, using protocols such as HTTPS or SSH. For example, you might use a Git repository over HTTPS like this:
module "policy" {
    source = "git::https://example.com/policy.git"
}
If you prefer to clone the any repository over SSH instead of HTTPS, you can use the following format:
module "policy" {
    source = "git::ssh://[email protected]/policy.git"
}
Alternatively, you can use the "scp-like" syntax that doesn't have the ssh:// scheme part but uses only the git:: part and colon (::) is used to separate the username from the path, rather than indicating a port number. An example follows:
module "policy" {
    source = "git::[email protected]/policy.git"
}
When Terraform installs a module from a Git repository, it uses the command git clone ${source} under the hood. It will, therefore, respect any local Git configuration, including any credentials setup on a system.
To clone or otherwise access a private Git repository, you must configure Git with some form of credential configuration. This can be done in one of two ways:
# Configure Username
git config --global user.name "demo"

# Configure Email
git config --global user.email "[email protected]"

# Configure Password
git config --global user.password "1234321"

# Save credentials permantely
git config --global credential.helper store

# List the saved credentials in your local
git config --list --show-origin
# Change location into .ssh
cd ~/.ssh

# Generate ssh key-pairs
ssh-keygen -o -t rsa -C "[email protected]"

# Add the public key into your Git Repository
By default, when Terraform uses a Git repository as a module source, it will clone the repository and use the default branch, the one referenced by HEAD. You can override this behavior by using the ref argument. The ref argument lets you provide a specific version of the repository to use. The value for ref can be any reference that the git checkout command accepts. Examples include:
module "policy" {
    source = "git::https://example.com/policy.git?ref=v1.0.0"
}
module "policy" {
    source = "git::https://example.com/policy.git?ref=89890jh87462976d84fdea54b47"
}
module "policy" {
    source = "git::https://example.com/policy.git?ref=develop"
}
When working with particularly large Git repositories, the initial download of that repository can take a very long period of time. To speed this process up for large Git repositories, Terraform supplies an option that allows you to create a shallow clone that will download only part of the repository history.
To perform a shallow clone, you can pass the depth argument with the Git URL. The depth argument corresponds to the --depth option when you are executing a Git clone over the command line. The depth argument will specify the number of commits you want to include in the history. For example, depth=1 tells git to include only the last recent commit in the history. Example :
module "policy" {
    source = "git::https://example.com/policy.git?depth=1&ref=v1.0.0"
}
NOTE: You cannot use SHA-1 Hash. Actually when using shallow clones, you are able to specify a branch name or tag in ref argument.

4. GitHub server

Terraform also allows you to pull modules from a GitHub server as sources for your modules. You can specify an unprefixed github.com URL as a source for your module. Terraform can then automatically detect GitHub repositories and treat them as Git sources. For example, the following Terraform configuration will clone the repository from GitHub server over HTTPS:
module "policy" {
    source = "github.com/repository/example"
}
If you would like to use SSH instead of HTTPS to clone the GitHub repository, you can do so with the following format:
module "policy" {
    source = "[email protected]:repository/example"
}

5. Bitbucket server

Terraform allows you to use Bitbucket server as sources for your modules. When you are using a URL from Bitbucket (bitbucket.org) without any prefixes in your Terraform configuration, then Terraform automatically recognizes it as a Bitbucket repository. Here's how you may do it:
module "policy" {
    source = "bitbucket.org/repository/example"
}

6. Fetching Archives over HTTP

Terraform has a handy helper for fetching and using archives like ZIP files as module sources via HTTP. If Terraform finds that a URL ends with any of the archive file extensions, automatically it interprets the content of that file as a compressed archive and uses it as source code for the module.
Terraform recognizes the following file extensions as archives:
When you specify a URL with one of these extensions, Terraform will automatically download the archive and use its contents as the source code for the module. For example:
module "role" {
    source = "https://example.com/role-modules.zip"
}
Here, Terraform will download the ZIP file from a given URL and take its contents as source code for the role module.
If the URL doesn't contain one of these extensions, but refers to an archive file, you can still tell Terraform to treat it as an archive using the archive argument, For example:
module "role" {
    source = "https://example.com/role-modules?archive=zip"
}
In this case, Terraform will treat the URL as a ZIP archive, even though the URL did not end with the .zip extension.

7. S3 Bucket

Terraform supports using archives stored in S3 as module sources. In order for you to make use of an S3 bucket as a module source, you need to declare the source URL in a specific syntax. The syntax for an S3 bucket being used as a source for a module is
s3::https://s3-<s3-region>-amazonaws.com/<module-name>/<file-name>.zip
Here,
Here is an example using an S3 bucket as a module source:
module "s3-module" {
    source = "s3::https://s3-eu-west-1.amazonaws.com/terraform-modules/s3.zip"
}
Here, Terraform will download the s3.zip archive from the eu-west-1 region of S3 bucket and use its contents as the module source.
NOTE: If your S3 bucket is in the us-east-1 region, you should not specify the region in the source argument.
Before Terraform can access the objects in S3, it needs to authenticate with AWS. In order to do that, it searches for AWS credentials in these locations in the given order:

8. GCS

You can use archives stored in Google Cloud Storage (GCS) as module sources in Terraform. To do this, you need to use a special syntax when you specify the source URL. Use the following syntax to use an GCS as a module source:
gcs::https://www.googleapis.com/storage/v1/<bucket-name>/<path-to-module>.zip
Here,
Here is an example of how you might use a GCS as a module source:
module "gcs-module" {
    source = "gcs::https://www.googleapis.com/storage/v1/demomodule.zip"
}
In this example, the source attribute specifies the GCS bucket and archive file containing the module source code.
Terraform will be able to access the files in GCS after its successful authentication with Google Cloud Platform using credentials. You can set these credentials with one of the following:

2. Version

When using modules from a module registry, it is a good practice to specify the acceptable version numbers to avoid unexpected changes or conflicts. You can specify which version of a module to use using the version argument of the module block.
The version argument in a Terraform module block follows a version constraint string. When Terraform encounters a module block with a version argument, it performs the following:
Here is an example module block with a version argument.
module "gcs-module" {
    source = "gcs::https://www.googleapis.com/storage/v1/demomodule.zip"
    version = "20.17.2"
}
This block tells Terraform to use version 20.17.2 of the eks module from the terraform-aws-modules namespace in the Public Registry.
However, let's say the local system has a different version of the module installed. The internal representation of the module in the .terraform/modules/modules.json file shows:
{ 
    "Modules" = [
        {
            "Key" = "eks"
            "Source" = "registry.terraform.io/terraform-aws-modules/eks/aws"
            "Version" = "20.16.0"
            "Dir" = ".terraform/modules/eks"
        }
    ]
}
Here, the version of the module installed on the local system is 20.16.0, which does not match the specified version 20.17.2. In this case, Terraform will download the newest version of the module that meets the constraint from the module registry into the .terraform/modules.
Version constraints only work with modules installed from a module registry such as Public Terraform Registry and HCP Terraform's private module registry. Other types of module sources might have their own way of handling versioning, or they might not support versions at all. Consider a repository server that is using git, which tracks versions by tags, commits, or branches rather than version numbers.
NOTE: Versioning is not supported for modules sourced from a local file path.

Accessing Modules in Package Sub-directories

When a package is used as module source, e.g an archive file, the module itself may be nested in a sub-directory in the package. To access a module in a sub-directory, use double-slash syntax:
<package_source>//<module-path>
Terraform downloads the entire package to your local disk and then reads the module from the specified sub-directory.

Examples

Accessing Module Output Values

In Terraform, when you create a module, all the resources within that module are, in a sense, encapsulated. In other words, they are not directly accessible from the calling module. Well, this is a good thing, which lets you keep your module's internal implementation details private and organized.
However, there may be cases where you want to expose certain values or results from your module to the calling module. This is where output values come in. An output value is a way for a child module to selectively export certain values to be accessed by the calling module.
Let me illustrate this with an example. Suppose we have a child module called policy, which creates an AWS IAM policy and outputs the policy ARN. Now, in our parent module (e.g., main.tf) we need to reference the output value of the s3_policy_arn from our child module policy and use it to create an IAM role policy attachment.
// File: policy/main.tf (Child Module)
resource "aws_iam_policy" "policy" {
    name = "s3-access-policy"
    path = "/"
}
output "s3_policy_arn" {
    value = aws_iam_policy.s3.arn
    description = "The password used for db_instance creation"
}
// File: main.tf (Root Module)
module "policy-module" {
    source = "./policy"
}
resource "aws_iam_role_policy_attachment" "role-attachment" {
    policy_arn = module.policy-module.s3_policy_arn
    role = "IAM-role"
}
Here we are leveraging the syntax module.policy.s3_policy_arn to reference the output value of s3_policy_arn from the child policy module. We then use this to populate the policy_arn attribute of the aws_iam_role_policy_attachment resource.

Removing a Module

In order to destroy a module in Terraform, you need to remove the module call from your Terraform configuration. This will remove the module's resources from the Terraform statefile, and by default, Terraform assumes you want to destroy the resources managed by that module. This is because the module configuration was removed from your Terraform configuration. Hence, Terraform no longer has knowledge of those resources.
However, there may be cases where you want to remove a module from your Terraform configuration without destroying any real infrastructure objects that the module manages. This is useful when you might want to refactor your Terraform code or move resources to other modules without touching the actual infrastructure.
You could accomplish the above using the removed block. The removed block enables the ability to remove a module from your Terraform configuration without having to destroy the resources it manages. The removed block comes in two parts:
Here is an example of the removed block:
removed {
    from = module.vpc
    lifecycle {
        destroy = false
    }
}

Related Pages

Feedback

Was this page helpful?