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