Mastering Terraform Infrastructure as Code
Why Terraform Remains the Go-To Infrastructure Tool
When you are scaling cloud infrastructure—whether spinning up Kubernetes clusters for high-throughput microservices or deploying secure AI inference endpoints—clicking through AWS, Azure, or GCP consoles is a one-way ticket to operational chaos. Manual changes drift, environments mismatch between staging and production, and auditing who changed what becomes impossible.
As a hands-on DevOps and MLOps engineer at techsolss, I rely heavily on the terraform infrastructure as code tool to define immutable, version-controlled cloud environments. Terraform lets you express your desired infrastructure state in declarative HashiCorp Configuration Language (HCL), ensuring your staging clusters in local cloud regions and production nodes worldwide match down to the exact security group rule.
In this guide, we will skip the superficial feature lists and walk through a production-grade Terraform setup, manage state safely, and cover the practical patterns we use every day with our global clients.
Core Architecture and HCL Workflow
Terraform works by comparing your desired configuration files against the real-world infrastructure using a state file (terraform.tfstate). When you execute commands, the workflow is strictly divided into three phases:
- Init (
terraform init): Downloads required provider plugins (AWS, Azure, Kubernetes, etc.) and sets up backend storage. - Plan (
terraform plan): Queries the cloud provider APIs, compares the current state with your HCL files, and outputs a dry-run execution plan showing what will be added, modified, or destroyed. - Apply (
terraform apply): Executes the plan against the provider APIs.
Let us look at a simple, production-ready module that provisions an AWS VPC with public and private subnets, a pattern we frequently deploy during initial cloud cost optimization for startups initiatives to avoid wasteful over-provisioning.
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
ManagedBy = "Terraform"
Environment = var.environment
}
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-subnet-${count.index + 1}"
}
}
Managing State and Backends Safely
The most common pitfall when adopting the terraform infrastructure as code tool is local state management. If two engineers run terraform apply from their local laptops using a local terraform.tfstate file, state corruption and race conditions are guaranteed.
For production workflows, you must configure a remote backend with state locking (such as AWS S3 with DynamoDB locking, or Terraform Cloud). Here is how we configure an S3 backend in our code:
terraform {
backend "s3" {
bucket = "techsolss-terraform-state-prod"
key = "vpc/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
By leveraging DynamoDB for state locking, if an engineer or an automated CI/CD pipeline is running a plan or apply, any concurrent execution is blocked until the lock is released, preventing conflicting modifications.
Integrating Terraform into CI/CD Pipelines
Infrastructure changes should never happen ad-hoc from developer terminals. They belong in your version control and deployment pipelines. Whether you use GitHub Actions, GitLab CI, or Azure DevOps, your pipeline should automatically run terraform plan on Pull Requests and output the diff directly into the PR comments, leaving terraform apply for post-merge execution.
A typical pipeline step for validation and planning looks like this:
- name: Terraform Init & Validate
run: |
terraform init
terraform validate
- name: Terraform Plan
run: terraform plan -no-color -out=tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan
Automating this workflow ensures complete visibility and audit trails for every single infrastructure modification.
Modularizing for Scale and Maintainability
As your infrastructure grows, monolithic Terraform configurations become unmaintainable. Writing DRY (Don't Repeat Yourself) code requires breaking resources down into reusable modules.
For instance, rather than copying and pasting security group rules across multiple microservices or AI model endpoints, encapsulate them into a security_group module:
module "app_sg" {
source = "./modules/security_group"
vpc_id = aws_vpc.main.id
ingress_ports = [80, 443, 8080]
environment = var.environment
}
This modular approach keeps codebases clean, simplifies testing, and lets teams across your organization spin up standardized, compliant infrastructure in minutes.
Next Steps with Infrastructure Automation
Adopting a robust infrastructure as code workflow eliminates configuration drift and drastically cuts down environment provisioning time. If you are looking to audit your current cloud setup, migrate legacy infrastructure into reproducible code, or optimize your pipeline architecture, feel free to contact techsolss to discuss how our engineering team can help.
Want help with this in your own stack?
We build and run this in production for clients — and we’ll tell you honestly what it will take in yours. Book a free 20-minute call.
Book a free 20-min call