Skip to content
Terraform Guide — Infrastructure as Code with HCL

Terraform Guide — Infrastructure as Code with HCL

DodaTech Updated Jun 7, 2026 7 min read

Terraform is an infrastructure-as-code tool that lets you define, provision, and manage cloud resources across AWS, Azure, GCP, and hundreds of other providers using a declarative configuration language (HCL).

What You’ll Learn

  • Writing Terraform configurations with providers and resources
  • Using variables and outputs for reusable configurations
  • Understanding Terraform state and remote state management
  • Creating and using modules for reusable infrastructure
  • Executing the plan/apply workflow safely
  • Managing infrastructure across multiple environments

Why Terraform Matters

Clicking through cloud console UIs to create servers, databases, and networks is error-prone and unrepeatable. Terraform lets you define your entire infrastructure in code — version-controlled, reviewed, and applied consistently. When you need to recreate infrastructure, you run terraform apply, not manually recreate 50 resources from memory. DodaTech uses Terraform to manage Durga Antivirus Pro’s cloud infrastructure — the entire production environment (VPCs, EC2 instances, RDS databases, load balancers) is defined in Terraform configs, making disaster recovery a single command away.

    flowchart LR
    A[Bash & Cloud Basics] --> B[Terraform]
    B --> C[Providers & Resources]
    B --> D[State Management]
    B --> E[Modules]
    B --> F[Plan / Apply]
    C --> G[AWS / Azure / GCP]
    D --> H[Remote State]
    E --> I[Reusable Components]
    style B fill:#844fba,color:#fff
  
Prerequisites: Basic Bash and AWS command-line experience. A cloud provider account (AWS free tier recommended) for practice.

Core Concepts

Providers and Resources

# main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Configure the AWS provider
provider "aws" {
  region = "us-east-1"
}

# Define resources
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0" # Amazon Linux 2
  instance_type = "t2.micro"

  tags = {
    Name = "WebServer"
    Env  = "production"
  }
}

resource "aws_security_group" "web_sg" {
  name = "web-server-sg"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Plan / Apply Workflow

# Initialize the project (download providers)
terraform init

# Output:
# Initializing the backend...
# Initializing provider plugins...
# Terraform has been successfully initialized!

# Preview changes
terraform plan

# Output:
# Terraform will perform the following actions:
#   + aws_instance.web       create
#   + aws_security_group.web_sg create
# Plan: 2 to add, 0 to change, 0 to destroy.

# Apply changes
terraform apply
# Enter a value: yes

# Output:
# aws_security_group.web_sg: Creating...
# aws_security_group.web_sg: Creation complete
# aws_instance.web: Creating...
# aws_instance.web: Creation complete
# Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Output: terraform plan shows what will be created/changed/destroyed without making changes. terraform apply executes the plan. Always review the plan before applying — it catches accidental deletions.

Variables and Outputs

# variables.tf
variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "environment" {
  description = "Environment name (dev/staging/prod)"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "tags" {
  description = "Common tags for all resources"
  type        = map(string)
  default = {
    Project = "MyApp"
    ManagedBy = "Terraform"
  }
}
# outputs.tf
output "instance_public_ip" {
  description = "Public IP of the web server"
  value       = aws_instance.web.public_ip
}

output "instance_id" {
  value = aws_instance.web.id
}

output "connection_string" {
  value     = "ssh://ec2-user@${aws_instance.web.public_ip}"
  sensitive = false
}
# Set variables via command line or file
terraform apply -var="instance_type=t3.small" -var="environment=staging"

# Use a tfvars file
# terraform.tfvars
instance_type = "t3.medium"
environment   = "prod"

# Apply with tfvars
terraform apply -var-file="prod.tfvars"

State and Remote State

# backend.tf — store state in S3 (never use local state for teams)
terraform {
  backend "s3" {
    bucket         = "myapp-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}

# The state is now in S3, not locally
# Multiple team members can run Terraform safely
# DynamoDB prevents concurrent operations

Why remote state matters: With local state, if your laptop dies, your infrastructure definition is gone. With remote state in S3, your entire infrastructure definition is backed up, accessible to your team, and locked against concurrent modifications.

Modules

# modules/webserver/main.tf
variable "name" { type = string }
variable "instance_type" { type = string }
variable "subnet_id" { type = string }
variable "vpc_id" { type = string }

resource "aws_instance" "this" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type
  subnet_id     = var.subnet_id

  tags = { Name = var.name }
}

resource "aws_security_group" "this" {
  name   = "${var.name}-sg"
  vpc_id = var.vpc_id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

output "instance_ip" {
  value = aws_instance.this.public_ip
}
# production/main.tf — using the module
module "web_server" {
  source = "../modules/webserver"

  name          = "prod-web"
  instance_type = "t3.large"
  subnet_id     = aws_subnet.public.id
  vpc_id        = aws_vpc.main.id
}

output "web_ip" {
  value = module.web_server.instance_ip
}

Common Mistakes

  1. Storing secrets in variables or state: Terraform state often contains plaintext values of resource attributes (database passwords, access keys). Use sensitive = true and a secrets manager (AWS Secrets Manager, Vault).

  2. Running terraform apply without terraform plan: Always review the plan first. A typo in a variable can cause Terraform to destroy and recreate critical infrastructure.

  3. Not using remote state in teams: Local state causes conflicts when multiple people run Terraform. Use S3/Azure Storage/GCS backends with state locking.

  4. Hardcoding values instead of using variables: Hardcoding AMI IDs, instance types, and region values makes configurations inflexible. Always use variables with sensible defaults.

  5. Ignoring terraform destroy safety: terraform destroy deletes ALL resources defined in your configuration. Use -target to destroy specific resources, and never run destroy on production without extreme caution.

Practice Questions

  1. What is the purpose of terraform init? Answer: terraform init downloads provider plugins, initializes the backend (local or remote), and downloads modules. It’s the first command to run after cloning a Terraform repository.

  2. How does Terraform track the state of deployed resources? Answer: Terraform stores a state file (terraform.tfstate) that maps configuration resources to real-world infrastructure IDs. Remote backends (S3, Terraform Cloud) enable team collaboration.

  3. What is the difference between terraform plan and terraform apply? Answer: plan shows what changes will be made without executing them. apply executes the planned changes. Always review the plan before applying.

  4. How do variables make Terraform reusable across environments? Answer: Variables allow the same configuration to produce different infrastructure for dev, staging, and prod by changing variable values via tfvars files or command-line flags.

Challenge

Provision a three-tier application: create Terraform modules for VPC (networking), database (RDS PostgreSQL), and application (EC2 autoscaling group with ALB), use remote state in S3 with DynamoDB locking, create dev.tfvars and prod.tfvars with different instance sizes, and practice the full apply/destroy cycle.

FAQ

Is Terraform free?
: Terraform (OpenTofu) is open-source. Terraform Cloud has a free tier for up to 5 users. HashiCorp’s commercial version has additional enterprise features.
Can Terraform manage existing infrastructure?
: Yes. Use terraform import to bring existing resources under Terraform management. The resource must be defined in configuration first.
How does Terraform differ from Ansible?
: Terraform is declarative (desired state) and focused on infrastructure provisioning. Ansible is procedural (step-by-step) and focused on configuration management. They complement each other.
What is Terraform Cloud?
: Terraform Cloud is HashiCorp’s managed service with remote state, VCS integration, policy enforcement (Sentinel), and team collaboration.
What is OpenTofu?
: OpenTofu is the open-source fork of Terraform created after HashiCorp switched to the BSL license. It’s fully compatible with Terraform configurations.

Try It Yourself

# Install Terraform
# macOS: brew install terraform
# Linux: https://developer.hashicorp.com/terraform/install

mkdir terraform-demo && cd terraform-demo

# Create a minimal config
cat > main.tf << 'EOF'
terraform {
  required_providers {
    local = { source = "hashicorp/local" }
  }
}

resource "local_file" "demo" {
  content  = "Hello from Terraform!"
  filename = "${path.module}/hello.txt"
}
EOF

terraform init
terraform plan
terraform apply
cat hello.txt
terraform destroy

What’s Next

TopicDescription
Ansible
Configuration management alongside Terraform
AWS
Provision cloud infrastructure with Terraform

Related topics: Bash, AWS, Linux, Docker

What’s Next

Congratulations on completing this Terraform tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro