Skip to content

Terraform Basics

Terraform is an Infrastructure as Code (IaC) tool developed by HashiCorp. It uses the declarative configuration language HCL (HashiCorp Configuration Language) to define and manage cloud resources. With Terraform, you can describe your complete infrastructure architecture in code, enabling version control and repeatable deployments.

Install via the official repository (EL 9 / EL 10)
# Install the DNF plugin that provides config-manager
sudo dnf install -y dnf-plugins-core
# Add the HashiCorp official repository
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
# Install Terraform
sudo dnf install -y terraform
# Verify installation
terraform version
Terminal window
# Download the latest version (see https://developer.hashicorp.com/terraform/install for the current version)
TERRAFORM_VERSION="1.9.0" # example version — replace with the latest from the official site
wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip
# Extract and install
sudo dnf install -y unzip
unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip
sudo mv terraform /usr/local/bin/
sudo chmod +x /usr/local/bin/terraform
# Verify
terraform version
Terminal window
# Install auto-completion
terraform -install-autocomplete
# Reload the shell
source ~/.bashrc

Terraform configuration files use the .tf extension and HCL syntax.

# Comments start with #
# Block structure
resource "type" "name" {
param1 = "value1"
param2 = 123
# Nested block
nested_block {
key = "value"
}
}
# String interpolation
variable "name" {
default = "myserver"
}
resource "example" "demo" {
name = "prefix-${var.name}"
}
# String
name = "web-server"
# Number
count = 3
# Boolean
enabled = true
# List
availability_zones = ["cn-hangzhou-a", "cn-hangzhou-b"]
# Map
tags = {
Name = "web-server"
Environment = "production"
Team = "ops"
}

Providers are plugins that allow Terraform to interact with cloud platforms.

providers.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1" # Tokyo region
access_key = var.aws_access_key
secret_key = var.aws_secret_key
# Or use environment variables (recommended):
# export AWS_ACCESS_KEY_ID="your-access-key"
# export AWS_SECRET_ACCESS_KEY="your-secret-key"
}
providers.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
alicloud = {
source = "aliyun/alicloud"
version = "~> 1.220"
}
}
}
provider "alicloud" {
region = "cn-hangzhou"
access_key = var.alicloud_access_key
secret_key = var.alicloud_secret_key
}
# variables.tf - Variable definitions
variable "aws_access_key" {
description = "AWS Access Key"
type = string
sensitive = true
}
variable "aws_secret_key" {
description = "AWS Secret Key"
type = string
sensitive = true
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "instance_count" {
description = "Number of instances"
type = number
default = 1
}
# main.tf - Main configuration file
# Query the latest AlmaLinux 9 AMI
data "aws_ami" "almalinux9" {
most_recent = true
owners = ["764336703387"] # AlmaLinux official
filter {
name = "name"
values = ["AlmaLinux OS 9*x86_64*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Create a security group
resource "aws_security_group" "web" {
name = "web-sg"
description = "Allow HTTP and SSH"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "SSH"
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP"
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "web-security-group"
}
}
# Create EC2 instances
resource "aws_instance" "web" {
count = var.instance_count
ami = data.aws_ami.almalinux9.id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
dnf install -y nginx
systemctl enable --now nginx
echo "<h1>Server ${count.index + 1}</h1>" > /usr/share/nginx/html/index.html
EOF
tags = {
Name = "web-server-${count.index + 1}"
Env = "production"
}
}
# outputs.tf - Output definitions
output "instance_public_ips" {
description = "List of instance public IPs"
value = aws_instance.web[*].public_ip
}
output "instance_ids" {
description = "List of instance IDs"
value = aws_instance.web[*].id
}
output "ami_id" {
description = "AMI ID used"
value = data.aws_ami.almalinux9.id
}
# main.tf - Alibaba Cloud ECS example
variable "alicloud_access_key" {
type = string
sensitive = true
}
variable "alicloud_secret_key" {
type = string
sensitive = true
}
# Query available zones
data "alicloud_zones" "default" {
available_resource_creation = "VSwitch"
}
# Query images
data "alicloud_images" "almalinux" {
name_regex = "^almalinux_9"
most_recent = true
owners = "system"
}
# Create a VPC
resource "alicloud_vpc" "main" {
vpc_name = "tf-demo-vpc"
cidr_block = "172.16.0.0/16"
}
# Create a VSwitch
resource "alicloud_vswitch" "main" {
vswitch_name = "tf-demo-vsw"
vpc_id = alicloud_vpc.main.id
cidr_block = "172.16.1.0/24"
zone_id = data.alicloud_zones.default.zones[0].id
}
# Create a security group
resource "alicloud_security_group" "web" {
name = "tf-demo-sg"
vpc_id = alicloud_vpc.main.id
}
resource "alicloud_security_group_rule" "allow_ssh" {
type = "ingress"
ip_protocol = "tcp"
port_range = "22/22"
security_group_id = alicloud_security_group.web.id
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "allow_http" {
type = "ingress"
ip_protocol = "tcp"
port_range = "80/80"
security_group_id = alicloud_security_group.web.id
cidr_ip = "0.0.0.0/0"
}
# Create an ECS instance
resource "alicloud_instance" "web" {
instance_name = "tf-demo-ecs"
image_id = data.alicloud_images.almalinux.images[0].id
instance_type = "ecs.t6-c1m1.large"
security_groups = [alicloud_security_group.web.id]
vswitch_id = alicloud_vswitch.main.id
internet_max_bandwidth_out = 10
system_disk_size = 40
user_data = base64encode(<<-EOF
#!/bin/bash
dnf install -y nginx
systemctl enable --now nginx
EOF
)
tags = {
Name = "web-server"
Env = "demo"
}
}
output "public_ip" {
value = alicloud_instance.web.public_ip
}
Terminal window
# Enter the project directory
cd /opt/terraform/myproject
# Initialize (downloads Provider plugins)
terraform init
# Upgrade Provider versions
terraform init -upgrade
Terminal window
# View the plan (preview changes that will be made)
terraform plan
# Save the plan to a file
terraform plan -out=tfplan
# Use a variable file
terraform plan -var-file="production.tfvars"
# Pass a single variable
terraform plan -var="instance_count=3"
Terminal window
# Apply changes (requires confirmation)
terraform apply
# Use a saved plan file (no confirmation needed)
terraform apply tfplan
# Auto-approve (for CI/CD)
terraform apply -auto-approve
# Use a variable file
terraform apply -var-file="production.tfvars"
Terminal window
# Destroy all resources
terraform destroy
# Auto-approve destruction
terraform destroy -auto-approve
# Destroy only a specific resource
terraform destroy -target=aws_instance.web
Terminal window
# Format configuration files
terraform fmt
# Recursively format
terraform fmt -recursive
# Validate configuration syntax
terraform validate
# View current state
terraform show
# List all resources
terraform state list
# View details for a specific resource
terraform state show aws_instance.web[0]
# Refresh state (sync with actual remote state)
terraform refresh
# Generate a dependency graph
terraform graph | dot -Tpng > graph.png

Use .tfvars files to manage variables for different environments:

production.tfvars
instance_type = "t3.large"
instance_count = 3
staging.tfvars
instance_type = "t3.micro"
instance_count = 1
Terminal window
# Use different environment variables
terraform apply -var-file="production.tfvars"
terraform apply -var-file="staging.tfvars"

Pass sensitive information via environment variables:

Terminal window
# Terraform automatically reads environment variables with the TF_VAR_ prefix
export TF_VAR_aws_access_key="AKIAIOSFODNN7EXAMPLE"
export TF_VAR_aws_secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
terraform apply

The Terraform state file (terraform.tfstate) records the current state of the infrastructure. For team collaboration, a remote backend should be used to store the state file.

Terminal window
# State file is located in the project directory
ls -la terraform.tfstate
ls -la terraform.tfstate.backup
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-lock" # State locking
}
}
terraform {
backend "oss" {
bucket = "my-terraform-state"
prefix = "production"
region = "cn-hangzhou"
encrypt = true
tablestore_endpoint = "https://tf-state-lock.cn-hangzhou.ots.aliyuncs.com"
tablestore_table = "terraform-lock"
}
}
Terminal window
# List resources in the state
terraform state list
# View resource details
terraform state show aws_instance.web[0]
# Move a resource (rename)
terraform state mv aws_instance.web aws_instance.webserver
# Remove a resource from state (without deleting the actual resource)
terraform state rm aws_instance.web[0]
# Import an existing resource into Terraform management
terraform import aws_instance.web i-0abc123def456
# Pull remote state
terraform state pull > state.json
# Push state to remote
terraform state push state.json

Modules are used to encapsulate and reuse Terraform configurations.

Terminal window
# Module directory structure
mkdir -p modules/ecs-instance
modules/ecs-instance/variables.tf
variable "instance_name" {
description = "Instance name"
type = string
}
variable "instance_type" {
description = "Instance specification"
type = string
default = "ecs.t6-c1m1.large"
}
variable "image_id" {
description = "Image ID"
type = string
}
variable "vswitch_id" {
description = "VSwitch ID"
type = string
}
variable "security_group_id" {
description = "Security group ID"
type = string
}
variable "tags" {
description = "Resource tags"
type = map(string)
default = {}
}
modules/ecs-instance/main.tf
resource "alicloud_instance" "this" {
instance_name = var.instance_name
instance_type = var.instance_type
image_id = var.image_id
vswitch_id = var.vswitch_id
security_groups = [var.security_group_id]
internet_max_bandwidth_out = 10
system_disk_size = 40
tags = var.tags
}
modules/ecs-instance/outputs.tf
output "instance_id" {
value = alicloud_instance.this.id
}
output "public_ip" {
value = alicloud_instance.this.public_ip
}
output "private_ip" {
value = alicloud_instance.this.private_ip
}
# main.tf - Using a custom module
module "web_server" {
source = "./modules/ecs-instance"
instance_name = "web-server-01"
instance_type = "ecs.t6-c1m1.large"
image_id = data.alicloud_images.almalinux.images[0].id
vswitch_id = alicloud_vswitch.main.id
security_group_id = alicloud_security_group.web.id
tags = {
Role = "web"
Env = "production"
}
}
module "api_server" {
source = "./modules/ecs-instance"
instance_name = "api-server-01"
instance_type = "ecs.c6.large"
image_id = data.alicloud_images.almalinux.images[0].id
vswitch_id = alicloud_vswitch.main.id
security_group_id = alicloud_security_group.web.id
tags = {
Role = "api"
Env = "production"
}
}
output "web_server_ip" {
value = module.web_server.public_ip
}
output "api_server_ip" {
value = module.api_server.public_ip
}
# Using a module from the Terraform Registry
module "vpc" {
source = "alibaba/vpc/alicloud"
version = "1.10.0"
vpc_name = "production-vpc"
vpc_cidr = "10.0.0.0/8"
}
terraform-project/
├── environments/
│ ├── production/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── staging/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── modules/
│ ├── ecs-instance/
│ ├── vpc/
│ └── security-group/
└── .gitignore

Create a .gitignore for your Terraform project:

Terminal window
cat > .gitignore << 'EOF'
# Terraform
.terraform/
*.tfstate
*.tfstate.backup
*.tfplan
*.tfvars
!example.tfvars
# Sensitive files
*.pem
*.key
EOF

Terraform is the core tool for Infrastructure as Code, bringing cloud resource creation and management under version control. Combined with Cloud-Init for passing user-data and Ansible for subsequent configuration, these three tools together form a complete automated infrastructure delivery pipeline.