Terraform is an Infrastructure as Code (IaC) tool used to provision and manage infrastructure. It helps define and deploy resources across various cloud providers using code, making it easier to maintain and scale infrastructure.
Welcome to our tutorial series where we dive into cloud infrastructure deployment using Terraform or OpenTofu on AWS. In this series, the fundamentals are shown, guiding you through the process of minimizing resource usage and simplifying the deployment complexities associated with cloud infrastructure. This tutorial series is a work in progress.
This comprehensive OpenTofu and Terraform tutorial guides you step-by-step through creating infrastructure in AWS using Terraform.
Infrastructure as Code (IaC) helps maintain consistency, enables version control, enhances collaboration among teams, allows for easier replication of environments, streamlines the deployment and management of infrastructure boosting efficiency, and reducing errors in managing complex systems.
How to start building AWS infrastructure with Terraform: Terraform Basics
Install Terraform or Open Tofu, Create an AWS account, and install the AWS CLI
A basic introduction to Terraform and HCL (HashiCorp Configuration Language).
Basic Terraform commands.
Terraform configurations are portable across cloud providers? All Cloud Resources need to be managed by Terraform
Other tutorials for creating infrastructure in AWS using Terraform.
Terraform reads configuration files written in HCL (HashiCorp Configuration Language) that define infrastructure. Terraform supports many different cloud providers, each provider along with its release is defined in the Terraform configuration file, and a plugin binary supporting the integration is downloaded during the initialization phase.
Previously named OpenTF, OpenTofu is a fork of Terraform that is open-source, community-driven, and managed by the Linux Foundation, it was created in response to HashiCorp’s switch from an open-source license to the BUSL. OpenTofu is a drop-in replacement for Terraform.
Terraform and OpenTofu are currently equivalent. Choose Terraform or OpenTofu and use it consistently throughout the tutorial. If you decide to switch tools, read How to Migrate Infrastructure from Terraform to OpenTofu.
The provider acts as a translator between Terraform and the API of the infrastructure or service that Terraform will manage.
HashiCorp Configuration Language (HCL) is a declarative, human-readable language used to configure various HashiCorp tools like Terraform / OpenTofu, Vault, Nomad, and Consul. It aims to be both user-friendly and machine-parseable, making it an excellent choice for infrastructure as code (IaC) and other configuration management tasks.
During the tutorial, most of the elements of HCL will be explained and included in the examples.
Key elements of HCL for Terraform:
Terraform reads all the files ending in .tf and .tfvars located inside the directory where the CLI is running, it also reads all the subdirectories recursively. Files naming, folder organization, or ordering doesn't affect the execution.
Terraform configuration files .tf
hold Terraform blocks (resources, variables, modules) that define infrastructure.
The Terraform state file is a record of the infrastructure managed by Terraform. It keeps track of the resources Terraform manages and their current state. This file is essential for Terraform to understand the changes made to infrastructure over time and to plan and execute updates accurately. The file can be stored locally (local state file) or shared with the team in a remote repository (remote backend).
Terraform files could have confidential information and therefore should be shared carefully.
Variable values files use the .tfvars
extension and are used to set the value of variables.
In Terraform, a "block" refers to a specific section of your configuration file that defines a resource, a provider, a variable, an output, or other configuration elements. These blocks serve as the building blocks for infrastructure as code (IaC) projects in Terraform and are written within curly braces { }
Types of Terraform Blocks:
Configures the connection to a cloud provider or service like AWS, Azure, GCP, or OCI. The provider block is where the required providers are configured. In this tutorial, the AWS provider is used. (The provider is defined inside the Terraform block, not here)
Provider example: shows the minimal configuration for the AWS provider, specifying a list of credential files, which provider (from the credentials file) to use, and the AWS region this provider will manage.
provider "aws" { shared_credentials_files = ["~/.aws/credentials"] profile = "ditwl_infradmin" region = "us-east-1" } }
Defines a specific infrastructure resource like an AWS EC2 instance, an Azure VM, or a DigitalOcean droplet.
Resource example: shows a resource block that defines an "aws_vpc
" resource named "ditlw-vpc
" with the attributes cidr_block
require a string representing a CIDR IP range, a comment using # is shown after the value, and a map of strings representing tags
in the form tag name = tag value.
resource "aws_vpc" "ditlw-vpc" { cidr_block = "172.21.0.0/19" #172.21.0.0 - 172.21.31.254 tags = { Name = "ditlw-vpc" InfraID = "123" } }
The resource can be referenced in other blocks by using its type and name, for example, the vpc_id value is set using a reference to the id output property of the previous resource aws_vpc.ditlw-vpc
:
resource "aws_internet_gateway" "ditwl-ig" { vpc_id = aws_vpc.ditlw-vpc.id }
It holds a named value that can be referenced throughout the configuration. Variables increase reusability, reduce errors, and improve flexibility, enabling environment management.
Variables are declared in Terraform variable definition files ( .tfvars) and values are set in configuration files (.tf).
Example:
aws-terraform-tutorial.tfvars: a Terraform variable definition file with two variables, the first one named provider_default_aws_account_id
of type list of stings with an empty default value, and the second named aws_sn_zb_pro_pub_36
with type map and no default value, forcing users to set a value.
variable "provider_default_aws_account_id" { description = "List of allowed AWS account IDs" type = list(string) default = [] } variable "aws_sn_zb_pro_pub_36" { description = "Zone: A, Env: PRO, Type: PUBLIC, Code: 32" type = map }
aws-terraform-tutorial.tf a Terraform configuration file that sets the value of provider_default_aws_account_id
and aws_sn_zb_pro_pub_36
.
provider_default_aws_account_id = ["1234567890"] resource "aws_vpc" "ditlw-vpc" { cidr_block = "172.21.0.0/19" #172.21.0.0 - 172.21.31.254 tags = { Name = "ditlw-vpc" } }
Exposes a specific value from the configuration for external use. It is mostly used in modules and for printing information from the created resources.
Example: an IAM user is created and the value of the Encrypted Secret access key is stored in an output block.
# Create an Access Key for the user and encrypt its value using PGP public key resource "aws_iam_access_key" "project_1_admin" { user = aws_iam_user.project_1_admin.name pgp_key = file("~/keys/itwonderlab.com/ditwl_infradmin_gpg_b64_public.key") } # Store the Encrypted Secret access key in an Output variables output "project_1_admin_encrypted_secret_access_key" { value = aws_iam_access_key.project_1_admin.encrypted_secret }
The value of the output variable can be used in automation pipelines, for example, the Encrypted Secret access key is exported as raw text, base64 decoded, decrypted, and stored in a file.
terraform output -raw project_1_admin_encrypted_secret_access_key | base64 --decode | gpg --decrypt --output .private/project_1_admin_encrypted_secret
Specify key-value pairs to define properties within a block. See previous that set block attributes.
Expressions are computed values produced by functions or references:
Terraform includes many built-in functions for data and string manipulation.
Example: generate a map from multiple maps.
tags = merge( var.instance_tags, { "private_name" = var.tag_private_name, "public_name" = var.tag_public_name, "environment" = var.tag_environment, "app" = var.tag_app, "app_id" = var.tag_app_id, "os" = var.tag_os, "os_id" = var.tag_os_id, "cost_center" = var.tag_cost_center "Name" = var.name } )
Access values from other resource definitions, outputs, or properties. References are used to specify output from one resource into another.
Arithmetic and Logical Operators are used for basic arithmetic (e.g. add numbers), test and compare values (e.g. equal, greater), and perform logical boolean operations.
For choosing between two values depending on the evaluation of the condition.
Example:
disable_api_termination = var.is_production ? true : false
Iterates over a list of elements applying a function to each value.
Creates block values by iterating over a list of elements.
Add explanatory notes to your configurations using the #
symbol.
Terraform is a command line utility that can be scripted for CI/CD pipelines or used in Terraform Automation and Collaboration tools (TACOS) like env0 or Terraform Cloud.
In this tutorial, Terraform is used through the command line but can be later moved into a CI/CD pipeline or a TACOS environment as the configuration is completely portable.
Terraform and OpenTofu are currently equivalent. Choose Terraform or OpenTofu and use it consistently throughout the tutorial. If you decide to switch tools, read How to Migrate Infrastructure from Terraform to OpenTofu.
Terraform CLI (Command Line Interface) has 3 basic commands:
Terraform init prepares the environment:
Example:
$ terraform init Initializing the backend... Initializing provider plugins... - Finding hashicorp/aws versions matching "~> 5.0"... - Installing hashicorp/aws v5.31.0... - Installed hashicorp/aws v5.31.0 (signed by HashiCorp) Terraform has made some changes to the provider dependency selections recorded in the .terraform.lock.hcl file. Review those changes and commit them to your version control system if they represent changes you intended to make. Terraform has been successfully initialized! ...
$ tofu init Initializing the backend... Initializing provider plugins... - Finding hashicorp/aws versions matching "~> 5.0"... - Installing hashicorp/aws v5.31.0... - Installed hashicorp/aws v5.31.0 (signed, key ID 0C0AF313E5FD9F80) Providers are signed by their developers. If you'd like to know more about provider signing, you can read about it here: https://opentofu.org/docs/cli/plugins/signing/ OpenTofu has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that OpenTofu can guarantee to make the same selections by default when you run "tofu init" in the future. OpenTofu has been successfully initialized! ...
Terraform plan compares the local (or remote) state file with the real infrastructure, pointing out the differences in 3 categories:
$ tofu plan aws_subnet.ditwl-sn-za-pro-pub-00: Refreshing state... [id=subnet-08de387c24b922975] aws_vpc.ditlw-vpc: Refreshing state... [id=vpc-069d2b63cdd839343] OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create ~ update in-place - destroy OpenTofu will perform the following actions: # aws_subnet.ditwl-sn-za-pro-pri-02 will be created + resource "aws_subnet" "ditwl-sn-za-pro-pri-02" { + availability_zone = "us-east-1a" + availability_zone_id = (known after apply) + cidr_block = "172.21.2.0/23" + tags = { + "Name" = "ditwl-sn-za-pro-pri-02" } + tags_all = { + "Name" = "ditwl-sn-za-pro-pri-02" + "cost_center" = "sales-department" + "environment" = "pro" } + vpc_id = "vpc-069d2b63cdd839343" } # aws_subnet.ditwl-sn-za-pro-pub-00 will be destroyed # (because aws_subnet.ditwl-sn-za-pro-pub-00 is not in configuration) - resource "aws_subnet" "ditwl-sn-za-pro-pub-00" { - arn = "arn:aws:ec2:us-east-1:431960152202:subnet/subnet-08de387c24b922975" -> null ... - availability_zone = "us-east-1a" -> null - availability_zone_id = "use1-az1" -> null - cidr_block = "172.21.0.0/23" -> null - vpc_id = "vpc-069d2b63cdd839343" -> null } # aws_vpc.ditlw-vpc will be updated in-place ~ resource "aws_vpc" "ditlw-vpc" { id = "vpc-069d2b63cdd839343" ~ tags = { "Name" = "ditlw-vpc" + "tool" = "Terraform" } ~ tags_all = { + "tool" = "Terraform" # (3 unchanged elements hidden) } # (14 unchanged attributes hidden) } Plan: 1 to add, 1 to change, 1 to destroy. ──────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so OpenTofu can't guarantee to take exactly these actions if you run "tofu apply" now.
Terraform apply executes the changes defined in the infrastructure configuration. It takes the plan and applies it to the actual cloud infrastructure, adding, changing, or destroying resources needed to match the configuration defined in the Terraform Configuration file/s. This command brings the desired state of the infrastructure to reality.
The output of the command shows the planned changes indicating what will be created (+), changed (~) or destroyed (-). User confirmation for the desired changes is asked before proceeding with the execution.
$ tofu apply (output has been redacted) OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create OpenTofu will perform the following actions: # aws_subnet.ditwl-sn-za-pro-pub-00 will be created + resource "aws_subnet" "ditwl-sn-za-pro-pub-00" { + arn = (known after apply) + availability_zone = "us-east-1a" + cidr_block = "172.21.0.0/23" + ipv6_native = false + tags = { + "Name" = "ditwl-sn-za-pro-pub-00" } + tags_all = { + "Name" = "ditwl-sn-za-pro-pub-00" + "cost_center" = "sales-department" + "environment" = "pro" } + vpc_id = (known after apply) } # aws_vpc.ditlw-vpc will be created + resource "aws_vpc" "ditlw-vpc" { + arn = (known after apply) + cidr_block = "172.21.0.0/19" + instance_tenancy = "default" + tags = { + "Name" = "ditlw-vpc" } + tags_all = { + "Name" = "ditlw-vpc" + "cost_center" = "sales-department" + "environment" = "pro" } } Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? OpenTofu will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_vpc.ditlw-vpc: Creating... aws_vpc.ditlw-vpc: Creation complete after 4s [id=vpc-069d2b63cdd839343] aws_subnet.ditwl-sn-za-pro-pub-00: Creating... aws_subnet.ditwl-sn-za-pro-pub-00: Still creating... [10s elapsed] aws_subnet.ditwl-sn-za-pro-pub-00: Creation complete after 13s [id=subnet-08de387c24b922975] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
No, Terraform Configurations are portable across the same cloud provider (changing the region) but not across different cloud providers as each one has its own set of services.
No, Terraform only manages the resources that are listed in its configuration files. Any resource that is created in the Cloud outside Terraform is not affected by Terraform operations but might create dependencies and be affected by changes (e.g. an EC2 instance is created using the AWS console, Terraform is not aware of the instance but using Terraform to destroy the VPC will trigger an error as it can't be deleted with EC2 instances using it).
This tutorial series is a work in progress and will have these sections:
AWS with Terraform: The Essential Guide: Sections
IT Wonder Lab tutorials are based on the diverse experience of Javier Ruiz, who founded and bootstrapped a SaaS company in the energy sector. His company, later acquired by a NASDAQ traded company, managed over €2 billion per year of electricity for prominent energy producers across Europe and America. Javier has over 25 years of experience in building and managing IT companies, developing cloud infrastructure, leading cross-functional teams, and transitioning his own company from on-premises, consulting, and custom software development to a successful SaaS model that scaled globally.