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

- Source: https://www.itwonderlab.com/aws-terraform-tutorial-terraform-basics/
- Published: 2024-01-01
- Updated: 2026-10-05
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## How to start building AWS infrastructure with Terraform

**[Terraform](https://www.itwonderlab.com/tag/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](https://www.itwonderlab.com/tag/aws-terraform-essential-guide/)**. 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 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.](https://www.itwonderlab.com/media/tutorials/AWS-Terraform-Essentials/ITWL-Tutorials-AWS-Terraform-Essentials-Steps.svg)

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

-   [Install Terraform or OpenTofu](https://www.itwonderlab.com/how-to-install-opentofu/).
-   AWS account, see [How to Create an AWS Account for demos](https://www.itwonderlab.com/create-an-aws-account-for-demos/).
-   AWS CLI profile, see [How to Install AWS CLI V2](https://www.itwonderlab.com/install-aws-cli/).
-   Having a remote version control system is suggested (e.g. [GitHub](https://github.com/)).
-   A code editor, the free [Microsoft Visual Studio Code](https://code.visualstudio.com/) is recommended.
-   Read [IT Wonder Lab terms of use](https://www.itwonderlab.com/terms-of-use/).

### 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](https://opentofu.org/) 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](https://www.itwonderlab.com/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](https://www.terraform.io/) / [OpenTofu](https://opentofu.org/), [Vault](https://www.vaultproject.io/), [Nomad](https://www.nomadproject.io/), and [Consul](https://www.consul.io/). 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](https://www.itwonderlab.com/aws-terraform-tutorial-terraform-modules/) 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.

```hcl
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.

```yaml
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`:

```hcl
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.

```hcl title="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.

```hcl title="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.`:

```hcl title="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](https://www.itwonderlab.com/terraform-aws-iam-users/) and the value of the Encrypted Secret access key is stored in an output block.

```hcl
# 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.

```bash
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](https://developer.hashicorp.com/terraform/language/functions) for data and string manipulation.

Example: generate a map from multiple maps.

```yaml
  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](https://www.itwonderlab.com/share-terraform-projects/): 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](https://www.env0.com/), [Spacelift](https://spacelift.io/) or HCP Terraform (formerly Terraform Cloud). A pipeline built with GitHub Actions is shown in [Terraform CI/CD](https://www.itwonderlab.com/aws-terraform-tutorial-terraform-cicd/).

![The Terraform workflow: init, plan and apply turn the .tf files into AWS resources and keep the state file up to date](https://www.itwonderlab.com/media/tutorials/AWS-Terraform-Essentials/ITWL-AWS-Terraform-Terraform-Workflow.svg "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.

![AWS Terraform Essentials Provider](https://www.itwonderlab.com/media/tutorials/AWS-Terraform-Essentials/ITWL-Tutorials-AWS-Terraform-Essentials-Provider.svg)

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](https://www.itwonderlab.com/aws-terraform-tutorial-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:

```shell title="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!
...
```

```shell title="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.

```shell
$ 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.

```shell
$ 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](https://www.itwonderlab.com/aws-terraform-tutorial-aws-basics/), or see the [full series index](#series).
