Terraform Concepts with a Hands-On Projec

Terraform Concepts with a Hands-On Project

In the article below, I’ve created and documented a hands-on project to help newcomers grasp the basics of Terraform. All instructions as well as resources are included below. I hope they help you get up and running with this valuable technology. Happy terraforming!

The Project

In this Terraform Hands-On Project we build a sample furniture store website hosted on an EC2 Instance. The EC2 Instance is provisioned by an Auto-Scaling Group (ASG), which sits behind an Application Load Balancer (ALB). The ASG ensures that a minimum of 2 instances are always running, scaling up to 4 instances based on load.

The IaC code is ready out-of-the-box, and available on my GitHub. I have provided detailed instructions in the ReadMe section of the GitHub project as well. The rest of this article summarizes 12 key Terraform concepts you can learn by creating this project yourself.

The Architecture

The architecture, as mentioned in the previous section, is fairly straightforward. Most beginner tutorials in Terraform provision a single EC2 Instance with a simple “Hello World” output text. I wanted to take it up a level, and hence created a more advanced webpage (a dummy furniture website) that is provisioned automatically via an autoscaling group and protected via an Application Load Balancer. This also makes the Terraform code more interesting, and provides a wider opportunity to learn its concepts. So without further delay, let’s dive in and take a look at some important concepts below.

Concept #1: Declarative vs Procedural Language

Terraform is a declarative language, as opposed to a procedural one. This means you simply declare the infrastructure you want, without specifying the steps necessary to achieve it. Terraform takes care of this on your behalf to achieve your desired end state. This is different from a procedural language where you have to specify the steps needed to get to the end state. Let’s look at an example. Suppose you wanted to provision 1 EC2 Instance, using Terraform. The code would look like the example below:

resource "aws_instance" "example" {
 ami = "ami-12345678"
 instance_type = "t2.micro"
}        

If you executed this code, Terraform would gladly provision 1 EC2 Instance with the corresponding AMI and Instance type. If you executed the code 3 more times, it would NOT provision another 3 EC2 Instances. This is because your code is declaring only 1 EC2 Instance and Terraform is smart enough to understand that you only want 1 Instance. In the programming world, this concept is known as idempotency. If we used a procedural language or tool, such as Ansible, and executed this same code 3 times, it would provision 3 EC2 Instances.

Concept #2: The Terraform State File

The terraform.tfstate file is a file that Terraform uses to keep track of the infrastructure it provisions. It’s the very thing that allows it to be a declarative language as mentioned above. The file stores details about the created infrastructure, such as AWS Instance IDs, IP addresses, etc. and helps Terraform understand what already exists in the cloud to determine necessary changes. Terraform also uses the state file for reference, instead of querying the cloud provider for every action or comparison, thus reducing the number of API calls to the cloud provider. The state file is in JSON format, and looks like the example below:

{
  "version": 4,
  "terraform_version": "1.5.0",
  "resources": [
    {
      "type": "aws_instance",
      "name": "example",
      "instances": [
        {
          "attributes": {
            "id": "i-0abcd1234efgh5678",
            "ami": "ami-12345678",
            "instance_type": "t2.micro",
            "public_ip": "54.321.123.45"
          }
        }
      ]
    }
  ]
}        

Concept #3: The .terraform Folder

The .terraform folder is an internal directory that Terraform creates in your project. It contains important metadata, plugins, and configurations needed for Terraform to function properly. The .terraform folder is created when you run the terraform init command.

A few important concepts surrounding the .terraform folder:

  • The .terraform folder should NOT be committed to version control. Instead, add it to .gitignore. This is because it may contain sensitive information (such as API keys, credentials, or backend configs) about your configurations which can be exposed to an attacker. Other reasons include unnecessary bloat in your repository (stemming from large provider binary files from AWS, Azure, etc.) as well as versioning and compatibility issues if different developers use different plugin versions.
  • If Terraform is behaving strangely, you can reset it by deleting the folder and re-running terraform init

Concept #4: The .terraform.lock.hcl File

The terraform.lock.hcl file is Terraform’s dependency lock file. It ensures consistent provider versions across different environments, preventing unexpected issues caused by version mismatches. The purpose of the lock file is to:

  • Ensure Version Consistency: It locks the exact versions of Terraform providers used in the project. It prevents accidental upgrades that might break infrastructure.
  • Improve Team Collaboration: It ensures all team members use the same provider versions when running terraform init.
  • Enhance Security: It stores checksums for downloaded providers to detect tampering or corruption.

An example of the terraform.lock.hcl file is shown below:

provider "registry.terraform.io/hashicorp/aws" {
  version = "5.10.0"
  hashes = [
    "h1:xYzA1234...",
    "zh:abcd5678..."
  ]
}        

In the example above we see two properties:

  • version = "5.10.0" → Locks the AWS provider version.
  • hashes = [...] → Ensures the downloaded provider matches a verified checksum.

NOTE: Unlike the .terraform folder mentioned above, you SHOULD commit the terraform.lock.hcl file to version control, since this prevents different team members from using different provider versions, ensuring stability.

Concept #5: The main.tf File

The main.tf is a conventionally used Terraform configuration file that typically serves as the primary entry point for defining infrastructure resources in a Terraform project. It contains the core configuration, such as resource definitions, provider settings, and variables.

Terraform, however, does NOT require a specific file name (such as main.tf ) for your configuration files. When you run Terraform commands (terraform init, terraform apply, etc.), Terraform automatically loads all .tf files in the directory, regardless of their names and processes them in the following way:

  • Reads all .tf files in the current working directory.
  • Merges them into a single in-memory configuration.
  • Executes the plan based on the combined configuration.

It is good practice, nonetheless, to structure your terraform code using multiple .tf files in the working directory (such as main.tf, variables.tf , output.tf, etc.) as this improves readability, reusability, and collaboration)

Concept #6: The Terraform Init Command

The terraform init command initializes the working directory by setting up the necessary plugins, backend configuration, and modules. It is the first command you run in a new or existing project.

Running the terraform init command usually does the following:

  • The command scans the .tf files in the directory.
  • It downloads the required provider plugins into the .terraform folder.
  • It configures remote backends if specified.
  • It initializes modules if your project uses external Terraform modules. .
  • It prepares the environment for other Terraform commands like plan and apply.

The example below shows a sample output of running the terraform init command:

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions...
- Installing hashicorp/aws v5.10.0...
- Installed hashicorp/aws v5.10.0 (signed by HashiCorp)

Terraform has been successfully initialized!        

Concept #7: The Terraform Plan Command

The terraform plan command is used to preview changes that Terraform will make to your infrastructure before applying them. It helps you see what resources will be created, modified, or destroyed without actually making any changes. A simple example of a terraform plan command would look something like this:

Terraform will perform the following actions:

  # aws_instance.example will be created
  + resource "aws_instance" "example" {
      + ami           = "ami-12345678"
      + instance_type = "t2.micro"
      + id            = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.        

In the example above (not the one used in our project) the output shows a simple plan to create 1 resource, an EC2 Instance. Terraform also shows if any resources will be destroyed or added

Concept #8: The Terraform Apply Command

The terraform apply command is usually applied after the terraform plancommand above, and used to apply changes to your infrastructure based on the preceding execution plan. When you run terraform apply, Terraform goes through several steps to execute the changes:

  • Reads the Current State: Terraform first checks the Terraform state file (terraform.tfstate) to understand what resources currently exist.
  • Compares with Desired State: Terraform then compares the current infrastructure with what’s defined in the .tf configuration files.
  • Creates an Execution Plan (Like terraform plan): Terraform calculates what needs to be created, changed, or destroyed to match your desired state.
  • Prompts for Confirmation: Unless you use terraform apply -auto-approve, Terraform will ask for confirmation before proceeding.
  • Executes the Plan (Applies Changes) Creates new resources (if they don’t exist). Modifies existing resources (if they’ve changed).Destroys resources (if they were removed from the configuration).
  • Updates the Terraform State File: Once the infrastructure changes are applied, Terraform updates the terraform.tfstate file to reflect the new state of your infrastructure.

Below is an example output of the Terraform terraform apply command, once again following the simple example of provisioning an EC2 Instance:

aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Still creating... [20s elapsed]
aws_instance.example: Still creating... [30s elapsed]
aws_instance.example: Creation complete after 35s [id=i-0abcdef1234567890]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

public_ip = "3.85.104.123"
instance_id = "i-0abcdef1234567890"        

Concept #9: Resources in Terraform

Resources are the fundamental building blocks that define a piece of infrastructure that Terraform manages. Whenever you want to provision something using Terraform, you are technically specifying a resource for terraform to create. Resources represent real-world objects such as:

  • Compute Instances (e.g., AWS EC2, etc.)
  • Databases (e.g., AWS RDS, etc.)
  • Networking Components (e.g., AWS VPC, subnets, security groups)
  • Storage (e.g., S3 buckets, etc.)

Resources also follow a basic syntax structure, shown below:

resource "<PROVIDER>_<RESOURCE_TYPE>" "<RESOURCE_NAME>" {
  argument1 = value1
  argument2 = value2
}        

The example below shows a basic EC2 Instance being provisioned using the syntax above:

resource "aws_instance" "my_ec2" {
  ami           = "ami-0c55b159cbfafe1f0"  # Amazon Linux 2 AMI
  instance_type = "t2.micro"

  tags = {
    Name = "MyTerraformInstance"
  }
}        

Concept #10: Variables in Terraform

Variables are used to store values dynamically instead of hardcoding them in configuration files. They help make your Terraform code more flexible, reusable, and maintainable. Terraform supports three types of variables:

  • Input variables (variable): These allow users to pass values dynamically, instead of hardcoding them (such as region, instance type, port numbers)
  • Local variables (locals): These allow you to store temporary computed values within a configuration (such as combining strings, calculations)
  • Environment variables: These are set outside Terraform and used inside configurations (such as AWS credentials, backend settings)

In my hands-on project I’ve only used the input variables as it is a fairly-beginner level project. In my project, I’ve assigned a variable to port 80, as it occurs several times throughout the code, and I wanted to follow the best practice of DRY (Don’t Repeat Yourself).

I stored the variable in a file called variables.tf (another conventional practice). It looks like this:

variable "server_port" {
  description = "The port the server will use for HTTP requests"
  type        = number
  default     = 80
}        

In the main.tf the variable for the server port then looks like this:

resource "aws_security_group" "alb_sg" {
  name_prefix = "alb-sg"
  vpc_id      = data.aws_vpc.default.id

  ingress {
    from_port   = var.server_port
    to_port     = var.server_port
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }        

Concept #11: The Terraform Destroy Command

The terraform destroy command removes all infrastructure resources that were previously created using terraform apply. It ensures that all resources managed by Terraform in the current configuration are deleted. When you run terraform destroy, Terraform goes through several steps to execute the changes:

  • Terraform reads the current state file: Terraform checks the terraform.tfstate file to find all the resources that it manages.
  • It generates a destruction plan: It marks each resource for deletion (-) and checks for dependencies. If a resource depends on another, Terraform destroys them in the correct order.
  • It sends delete API calls to the Cloud provider: Terraform contacts the provider’s API (AWS, Azure, GCP, etc.) and sends DELETE requests.
  • It waits for resources to be destroyed: Some resources take time to be deleted (e.g., EC2 instances, databases, storage buckets). Terraform polls the cloud provider to check if deletion is complete.
  • It updates the State File: After successful deletion, Terraform removes the destroyed resources from the state file.
  • It confirms Destruction is complete: Once all deletions are successful, Terraform prints a success message on the console

Concept #12: The Terraform Output Functionality

The output functionality is used to display values after applying a configuration. Outputs can be useful for providing information to users, exposing values to other Terraform configurations, or integrating with external systems.

In this project, for example, I chose to output the URL of the Application Load Balancer so that I could quickly access the link and verify everything worked. The example below shows the code I used (I placed it in a separate folder called output.tf, once again, as part of file-naming best practices):

# Output ALB DNS Name
output "alb_dns_name" {
  value       = aws_lb.app_alb.dns_name
  description = "The DNS name of the Application Load Balancer."
}        

After the running the terraform apply command, the console would output the following code:

Outputs:

alb_url = "app-load-balancer-1234567890.us-east-1.elb.amazonaws.com"        

Finally, you can simply copy/paste this link in your web browser (make sure to add https://in front as we only specified this protocol) and voila! You should see the sample furniture store website popup!

Conclusion

Learning Terraform is a game-changer for anyone looking to master infrastructure as code (IaC). By working through this hands-on project, you’ve not only built a scalable and reliable furniture store website but also gained practical experience with fundamental Terraform concepts like declarative configuration, state management, and essential commands (init, plan, apply, and destroy). This structured, real-world approach reinforces how Terraform simplifies infrastructure provisioning, making it a powerful tool for automating deployments in any cloud environment. Now go out there and build some infrastructure!

Further Reading

This project was not created in a vacuum. I had the pleasure of consuming several books and web resources that helped me write this article. I would like to credit them below:

  1. Terraform Up and Running, by Yevgeniy Brikman: An excellent introductory read about Terraform — Yevgen does a great job of providing step-by-step guidance on this vast subject. For more information, check out their GitHub repository as well
  2. Terraform YouTube Course, by Sid Palas: Sid from DevOps Directive, provides an excellent introduction to Infrastructure as Code(IaC) and Terraform, from which you can follow along and build a web application like above, although with a different architecture.
  3. Terraform Hands-On Project, by Azeez Salu: Azeez is a cloud-career switcher who’s created several hands-on projects, available on his website. They are paid, but I’ve taken them and recommend it myself.
  4. EC2 Website User Scripts, by freeCodeCamp: The sample furniture website was not something I created myseld from scratch, but one I found in the link above.


要查看或添加评论,请登录

Gopal Das的更多文章

社区洞察

其他会员也浏览了