Back to articles
May 21, 2026

Infrastructure as Code with Terraform: From Zero to Production

What Is Infrastructure as Code? Infrastructure as Code (IaC) is the practice of managing and provisioning infrastructure through machine-readable configuration files rather than manual processes or…

Placeholder cover imagePhoto: Lorem Picsum / Unsplash

What Is Infrastructure as Code?

Infrastructure as Code (IaC) is the practice of managing and provisioning infrastructure through machine-readable configuration files rather than manual processes or interactive configuration tools. Instead of clicking through a cloud console, you write code that defines your servers, networks, databases, and more.

The benefits are clear: version-controlled infrastructure, repeatable deployments, peer-reviewed changes through pull requests, and the ability to destroy and recreate environments with a single command.

Why Terraform?

Terraform, now part of HashiCorp's ecosystem, is one of the most popular IaC tools. It uses a declarative language called HCL (HashiCorp Configuration Language) to describe desired infrastructure state. Terraform then computes the minimal set of changes needed to reach that state — and applies them safely.

Key strengths include:

  • Multi-cloud support — AWS, Azure, GCP, and 150+ providers via providers and registries.
  • State management — Tracks the real-world state of your infrastructure to plan accurate changes.
  • Plan/apply workflow — Review changes before they happen, preventing accidental modifications.

Project Structure

A well-organized Terraform project separates concerns by environment and resource type:

infrastructure/
├── main.tf          # Root module - providers and shared resources
├── variables.tf     # Input variable definitions
├── outputs.tf       # Output values
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── ecs/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── environments/
    ├── dev/
    │   └── main.tf
    └── prod/
        └── main.tf

Defining Your First Resources

Here's a minimal example that creates an AWS S3 bucket and an EC2 instance:

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "app_data" {
  bucket = "my-app-data-${var.environment}"

  tags = {
    Name        = "Application Data"
    Environment = var.environment
  }
}

resource "aws_instance" "app_server" {
  ami           = var.ami_id
  instance_type = "t3.micro"

  vpc_security_group_ids = [aws_security_group.app.id]
  subnet_id              = var.subnet_id

  tags = {
    Name = "app-server-${var.environment}"
  }
}

resource "aws_security_group" "app" {
  name_prefix = "app-sg-"

  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"]
  }
}

Variables and Outputs

Keep values flexible with variables:

variable "environment" {
  description = "Deployment environment"
  type        = string
  default     = "dev"

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

variable "ami_id" {
  description = "AMI ID for the EC2 instance"
  type        = string
}

Expose useful values as outputs:

output "bucket_arn" {
  description = "ARN of the S3 bucket"
  value       = aws_s3_bucket.app_data.arn
}

output "instance_public_ip" {
  description = "Public IP of the app server"
  value       = aws_instance.app_server.public_ip
}

The Workflow: Init, Plan, Apply

# Initialize the working directory, downloading providers and modules
terraform init

# Preview what changes Terraform will make
terraform plan -var-file=environments/dev.tfvars

# Apply the planned changes
terraform apply -var-file=environments/dev.tfvars

# Destroy all managed resources
terraform destroy

State Management Best Practices

Terraform's state file is the source of truth. Never edit it manually. Store it remotely for team collaboration:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "dev/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-lock"
  }
}

The S3 bucket stores the state, and DynamoDB provides state locking to prevent concurrent modifications.

Conclusion

Terraform transforms infrastructure management from a fragile, manual process into a reliable, automated practice. Start with a single resource, learn the plan/apply cycle, and gradually expand your IaC footprint. The discipline of treating infrastructure as code pays dividends in reliability, collaboration, and speed.