AWS with Terraform Tutorial: Terraform Basics (1)

· Updated · 15 min read · Terraform & OpenTofu Tutorials

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 comprehensive OpenTofu and Terraform tutorial guides you step-by-step through creating infrastructure in AWS using 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.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.
Terraform
Basics
Terraform...
AWS is the world’s leading cloud platform, it is used by a wide range of organizations, from startups to large enterprises, to power their online businesses. AWS offers a wide range of services, including computing, storage, database, networking, analytics, machine learning, and artificial intelligence.AWS is the world’s leading cloud platform, it is used by a wide range of organizations, from startups to large enterprises, to power their online businesses. AWS offers a wide range of services, including computing, storage, database, networking, analytics, machine learning, and artificial intelligence.
AWS
Basics
AWS...
The Terraform official AWS provider acts as an abstraction layer that lets Terraform configurations written in HCL define AWS services and infrastructure using code (IaC). Internally Terraform and the AWS provider handle authentication, and make the necessary AWS API calls to query, create, modify, and destroy the resources.The Terraform official AWS provider acts as an abstraction layer that lets Terraform configurations written in HCL define AWS services and infrastructure using code (IaC). Internally Terraform and the AWS provider handle authentication, and make the necessary AWS API calls to query, create, modify, and destroy the resources.
Terraform
AWS Provider
Terraform...
How to Create, and Manage AWS VPCs with TerraformHow to Create, and Manage AWS VPCs with Terraform
AWS VPC
AWS VPC
How to configure and use the Terraform aws_subnet resource block to create and manage AWS Subnets inside a VPC.How to configure and use the Terraform aws_subnet resource block to create and manage AWS Subnets inside a VPC.
AWS Subnets
AWS Subnets
How to configure and use the Terraform aws_internet_gateway resource block to create and manage AWS Internet Gateway inside a VPC to enable Internet access to and from instances. How to configure and use the Terraform aws_internet_gateway resource block to create and manage AWS Internet Gateway inside a VPC to enable Internet access to and from instances.
AWS Internet
Gateway
AWS Internet...
How to configure and use the Terraform aws_nat_gateway and aws_eip resource blocks to create and manage AWS NAT Gateway and its corresponding Public IPs inside each availability zone to enable Internet access from instances in private subnets.How to configure and use the Terraform aws_nat_gateway and aws_eip resource blocks to create and manage AWS NAT Gateway and its corresponding Public IPs inside each availability zone to enable Internet access from instances in private subnets.
AWS NAT
Gateway
AWS NAT...
How to configure and use the Terraform aws_route_table, aws_route, and aws_main_route_table_association resource blocks to create and manage AWS Routing Tables.How to configure and use the Terraform aws_route_table, aws_route, and aws_main_route_table_association resource blocks to create and manage AWS Routing Tables.
AWS Routing
Tables
AWS Routing...
How to configure and use the Terraform aws_security_group and aws_security_group_rule resource blocks to create and manage AWS Security Groups and secure the infrastructure.How to configure and use the Terraform aws_security_group and aws_security_group_rule resource blocks to create and manage AWS Security Groups and secure the infrastructure.
AWS Security
Groups
AWS Security...
How to configure and use the Terraform aws_key_pair resource block to create and manage AWS Key Pairs for performing SSH Public Key Authentication into EC2 instances.How to configure and use the Terraform aws_key_pair resource block to create and manage AWS Key Pairs for performing SSH Public Key Authentication into EC2 instances.
AWS Key
Pairs
AWS Key...
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.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.
AWS AMIs
AWS AMIs
Using the Terraform aws_instance resource block to configure, launch, and secure EC2 instances.Using the Terraform aws_instance resource block to configure, launch, and secure EC2 instances.
AWS EC2
Instances
AWS EC2...
AWS RDS
AWS RDS
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_route53_delegation_set, aws_route53_zone, and aws_route53_record resource blocks to configure DNS in AWS.
AWS Route 53
(DNS)
AWS Route 53...
Amazon EC2 Auto Scaling groups keep the right number of instances running, replace failed ones and scale with the load.Amazon EC2 Auto Scaling groups keep the right number of instances running, replace failed ones and scale with the load.
AWS Auto
Scaling
AWS Auto...
Elastic Load Balancing distributes the traffic between healthy instances. The tutorial creates an Application Load Balancer with HTTPS.Elastic Load Balancing distributes the traffic between healthy instances. The tutorial creates an Application Load Balancer with HTTPS.
AWS Load
Balancers
AWS Load...
This tutorial shows how to create infrastructure in AWS using Terraform and configure the operating system and applications using Ansible.This tutorial shows how to create infrastructure in AWS using Terraform and configure the operating system and applications using Ansible.
Terraform,
AWS & Ansible
Terraform,...
Modules package resources behind variables and outputs so they can be reused. The tutorial refactors the network into a module.Modules package resources behind variables and outputs so they can be reused. The tutorial refactors the network into a module.
Terraform
Modules
Terraform...
A backend stores the Terraform state. The tutorial uses an encrypted, versioned S3 bucket with state locking.A backend stores the Terraform state. The tutorial uses an encrypted, versioned S3 bucket with state locking.
Terraform
Backends
Terraform...
fmt, validate, TFLint, Trivy, Checkov, terraform-docs, Infracost and pre-commit: check the code before it reaches AWS.fmt, validate, TFLint, Trivy, Checkov, terraform-docs, Infracost and pre-commit: check the code before it reaches AWS.
Terraform
Tools
Terraform...
Run checks, plans and approved applies automatically with GitHub Actions and OIDC, without access keys.Run checks, plans and approved applies automatically with GitHub Actions and OIDC, without access keys.
Terraform
CI/CD
Terraform...
Select a tutorial section 
Select a tutorial sectio...

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.

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 was a drop-in replacement for Terraform when it was created, and the two tools remain largely compatible.

For the content of this tutorial Terraform and OpenTofu are equivalent. Choose one of them 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 located in the directory where the CLI is running, together with the variable files terraform.tfvars and *.auto.tfvars. Subdirectories are not read unless a module uses them. The names of the files and the order of the blocks inside them do not 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 profile (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, which requires 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 with variable blocks in the configuration files (.tf), and their values are set in variable definition files (.tfvars), on the command line or with environment variables.

Example:

variables.tf: a configuration file that declares two variables. The first one is named provider_default_aws_account_id, has type list of strings and an empty default value. The second one is named aws_sn_zb_pro_pub_36, has type map of strings and no default value, which forces users to set a value.

variables.tf
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: B, Env: PRO, Type: PUBLIC, Code: 36"
  type        = map(string)
}

terraform.tfvars: a variable definition file that sets the values of both variables. Terraform loads terraform.tfvars and the files named *.auto.tfvars automatically; other files are loaded with the -var-file option.

terraform.tfvars
provider_default_aws_account_id = ["1234567890"]

aws_sn_zb_pro_pub_36 = {
  cidr_block        = "172.21.4.0/23"
  availability_zone = "us-east-1b"
}

The values are used in the configuration with the prefix var.:

aws-terraform-tutorial.tf
resource "aws_subnet" "ditwl-sn-zb-pro-pub-36" {
  vpc_id            = aws_vpc.ditlw-vpc.id
  cidr_block        = var.aws_sn_zb_pro_pub_36["cidr_block"]
  availability_zone = var.aws_sn_zb_pro_pub_36["availability_zone"]
}
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 and the workspace; its usage 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, Spacelift or HCP Terraform (formerly Terraform Cloud). A pipeline built with GitHub Actions is shown in Terraform CI/CD.

The Terraform workflow: init, plan and apply turn the .tf files into AWS resources and keep the state file up to date
Terraform workflow

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.

Infrastructure Needed for the Application
Infrastructure Needed for the Applic...
Region
Region
User (You)
User (You)
Terraform CLI
Terraform CLI
Terraform Configuration (HCL)
Terraform Configurat...
Init, Plan and Apply Configuration to AWS Cloud
Init, Plan and Ap...
Write Infrastructure Definition Using Terraform
Write Infrastruct...
Download Providers and Modules from Terraform Registry
Download Provider...
Terraform Registry
Terraform Registry
Compare, Add, Change & Destroy Infrastructure 
Compare, Add, Chan...
AWS Cloud
AWS Cloud
Identify and Design  Infrastructure Needs
Identify and...
Text is not SVG - cannot display

Terraform CLI (Command Line Interface) has 3 basic commands:

Terraform init #

Terraform init prepares the environment:

  1. Initializes the working directory: creates a hidden .terraform directory where Terraform stores the downloaded providers and modules and the configuration of the backend.
  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 HCP Terraform or S3 (see Terraform Backends) 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. Creates the dependency lock file .terraform.lock.hcl: records the exact versions of the providers that were selected, so everyone who works on the project uses the same ones. Commit it to Git.
  6. Validates the configuration (basic): init checks the syntax of the configuration files. Use terraform validate for a complete validation.

Example:

Terraform
$ 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!
...
OpenTofu
$ 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 #

Continue with AWS Basics, or see the full series index.

#AWS #AWS Terraform Tutorial #OpenTofu #Terraform