AWS with Terraform Tutorial: Terraform Basics (1)

How to start building AWS infrastructure with Terraform

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.

Terraform Basics, AWS Basics, Terraform AWS Provider, AWS VPC, AWS Subnets, AWS Internet Gateway, AWS NAT Gateway, AWS Routing Tables, AWS Security Groups, AWS Key Pairs, AWS AMIs, AWS EC2 Instances, AWS RDS, AWS Route 53 (DNS), AWS Auto Scaling, AWS Load Balancers, Terraform AWS & Ansible, Terraform Modules, Terraform Backends, Terraform Tools, Terraform CI/CD.

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

  1. Prerequisites

    Install Terraform or Open Tofu, Create an AWS account, and install the AWS CLI

  2. Terraform Introduction

    A basic introduction to Terraform and HCL (HashiCorp Configuration Language).

  3. Terraform Execution

    Basic Terraform commands.

  4. Common Terraform Questions

    Terraform configurations are portable across cloud providers? All Cloud Resources need to be managed by Terraform

  5. Next Steps

    Other tutorials for creating infrastructure in AWS using Terraform.

Prerequisites

Terraform Introduction

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.

A Quick Introduction to the HashiCorp Configuration Language

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:

File types

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.

Configuration Files

Terraform configuration files .tf hold Terraform blocks (resources, variables, modules) that define infrastructure.

State file

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

Variable values files use the .tfvars extension and are used to set the value of variables.

Blocks

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:

Provider

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"
  }
}
Resource

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
}
Variable

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"
  }
}
Output

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
Attributes

Specify key-value pairs to define properties within a block. See previous that set block attributes.

Expressions

Expressions are computed values produced by functions or references:

Functions

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
    }
   )
References

Access values from other resource definitions, outputs, or properties. References are used to specify output from one resource into another.

  • Resource: access values from other blocks by specifying the resource type and its unique name.
  • Input variables: same functionality as variables but used as module inputs.
  • Local values: a way to avoid repeating the same values or expressions multiple times.
  • Child module outputs: access output variables from modules.
  • Data sources: Find existing infrastructure resources not managed in the Terraform configuration and make them available as input for managed infrastructure.
  • Filesystem and workspace info: information about the local filesystem, ussage is not recommended.
  • Block-local values: variables generated inside blocks by the expressions used.
Operators

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.

Conditions

For choosing between two values depending on the evaluation of the condition.

Example:

disable_api_termination = var.is_production ? true : false
For Iterations and Splat Expressions

Iterates over a list of elements applying a function to each value.

Dynamic Blocks

Creates block values by iterating over a list of elements.

Comments

Add explanatory notes to your configurations using the # symbol.

Terraform Execution

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.

ITWL Tutorials AWS Terraform Essentials Provider

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

Terraform init prepares the environment:

  1. Initializes the workspace: Creates a hidden .terraform directory within the working directory where Terraform stores state and downloaded modules.
  2. Downloads and installs required plugins: Terraform providers are implemented as plugins, and init automatically locates and installs any plugins required by your configuration files. This includes fetching the appropriate versions for the specific cloud provider or service (e.g., AWS, Azure, GCP).
  3. Initializes or migrates remote state backend (optional): If a remote state backend like Terraform Cloud or S3 has been configured or changed, init will configure the connection to the backend and migrate the remote state if needed.
  4. Downloads remote modules (optional): If the configuration uses any external Terraform modules (reusable code blocks), init downloads them and verifies their compatibility with your local environment.
  5. Runs the configured provisioners (optional): Provisioners are scripts or tools that configure resources directly after creation. If the configuration includes provisioners, init will run them in the context of the downloaded modules.
  6. Validates the configuration: Terraform init performs basic validation of the configuration files, checking for syntax errors and missing references.

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

Terraform plan compares the local (or remote) state file with the real infrastructure, pointing out the differences in 3 categories:

  • Add (+): the infrastructure resource needs to be created in the Cloud as it is declared in the Terraform configuration but doesn't exist in the Cloud or a change has been made that requires destroying and adding the resource again.
  • Change (~): the infrastructure resource has been modified in the Terraform configuration file and the change needs to be done in the cloud.
  • Destroy (-): the resource is no longer needed, it has been removed from the Terraform configuration file and needs to be removed from the cloud.
$ 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

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.

Common Terraform Questions

Terraform configurations are portable across cloud providers?

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.

All Cloud Resources need to be managed by Terraform

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).

Next Steps

This tutorial series is a work in progress and will have these sections:

Terraform Basics, AWS Basics, Terraform AWS Provider, AWS VPC, AWS Subnets, AWS Internet Gateway, AWS NAT Gateway, AWS Routing Tables, AWS Security Groups, AWS Key Pairs, AWS AMIs, AWS EC2 Instances, AWS RDS, AWS Route 53 (DNS), AWS Auto Scaling, AWS Load Balancers, Terraform AWS & Ansible, Terraform Modules, Terraform Backends, Terraform Tools, Terraform CI/CD.

AWS with Terraform: The Essential Guide: Sections

Leave a Reply

Your email address will not be published. Required fields are marked *


Related Cloud Tutorials

Securing your Infrastructure: Encrypting Terraform State Files with OpenTofu
Using the Terraform aws_route53_delegation_set, aws_route53_zone, and aws_route53_record resource blocks to configure DNS in AWS.
Using the Terraform aws_db_instance resource block to configure, launch, and secure RDS instances.
How to use the Terraform aws_instance resource block to configure, launch, and secure EC2 instances.
How to configure and use the Terraform aws_ami data source block to find and use AWS AMIs as templates (root volume snapshot with operating system and applications) for EC2 instances.
Javier Ruiz Cloud and SaaS Expert

Javier Ruiz

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.

Are you looking for cloud automation best practices tailored to your company?

linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram