Terraform’s power lies in its ability to provision real cloud infrastructure — virtual machines, networks, databases, DNS records, load balancers, and Kubernetes clusters — using declarative configuration. A Terraform configuration describes the desired end state, and Terraform calculates and executes the actions needed to reach that state. Key concepts in practice are: resources (infrastructure objects to create), data sources (read existing infrastructure without managing it), variables (parameterised configuration), outputs (values to display or pass to other modules), modules (reusable infrastructure components), and remote state (shared state for team environments). This guide demonstrates provisioning a complete multi-tier infrastructure (VPC, EC2 instances, RDS database, and security groups) on AWS from a RHEL 9 workstation using Terraform.

Prerequisites

  • Terraform installed on RHEL 9
  • AWS CLI configured with access credentials (aws configure)

Step 1 — variables.tf and terraform.tfvars

# variables.tf
variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}
variable "instance_type" {
  type    = string
  default = "t3.micro"
}
variable "db_password" {
  type      = string
  sensitive = true  # Redacted from plan output and logs
}
# terraform.tfvars — never commit this file to Git
region        = "us-east-1"
instance_type = "t3.small"
db_password   = "SecureRDSPass123!"

Step 2 — main.tf (VPC, EC2, RDS)

# main.tf
provider "aws" {
  region = var.region
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = { Name = "main-vpc" }
}

# Public subnet
resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${var.region}a"
  tags = { Name = "public-subnet" }
}

# EC2 web server
data "aws_ami" "rhel9" {
  most_recent = true
  owners      = ["309956199498"]  # Red Hat's AWS account
  filter {
    name   = "name"
    values = ["RHEL-9*"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.rhel9.id
  instance_type = var.instance_type
  subnet_id     = aws_subnet.public.id
  tags = { Name = "web-server" }
}

# RDS PostgreSQL
resource "aws_db_instance" "db" {
  engine               = "postgres"
  engine_version       = "16"
  instance_class       = "db.t3.micro"
  allocated_storage    = 20
  db_name              = "appdb"
  username             = "appuser"
  password             = var.db_password
  skip_final_snapshot  = true
  tags = { Name = "app-database" }
}

Step 3 — Outputs and Remote State

# outputs.tf
output "web_public_ip"  { value = aws_instance.web.public_ip }
output "db_endpoint"    { value = aws_db_instance.db.endpoint }

# Remote state in S3 (add to versions.tf)
terraform {
  backend "s3" {
    bucket  = "my-terraform-state"
    key     = "production/terraform.tfstate"
    region  = "us-east-1"
    encrypt = true
  }
}

Step 4 — Apply and Manage

terraform init       # Initialise with S3 backend
terraform plan -out=tfplan  # Save plan for review
terraform apply tfplan       # Apply saved plan

# After changes:
terraform show               # Show current state
terraform state list         # List all managed resources
terraform output             # Show output values

Conclusion

Terraform infrastructure provisioning on RHEL 9 enables reproducible, version-controlled cloud environments. The sensitive = true attribute on variable declarations prevents credentials from appearing in plan output and logs — a critical security practice when Terraform runs in CI/CD pipelines where logs are often accessible to the entire team. Always use remote state backends in S3 with DynamoDB state locking for team environments to prevent concurrent terraform apply runs from corrupting the state file.

Next steps: How to Install Terraform on RHEL 9, How to Install Ansible on RHEL 9, and How to Register a GitHub Actions Runner on RHEL 9.