Terraform for Beginners 2026: Infrastructure as Code in 30 Minutes

Terraform is an open-source infrastructure-as-code tool by HashiCorp that lets you define cloud resources — servers, databases, networks, DNS records, and more — in plain-text configuration files instead of clicking through web consoles. You describe what you want, Terraform figures out how to build it, and your entire infrastructure becomes version-controlled, repeatable, and auditable. In a field where a single misconfigured security group can expose customer data and a forgotten manual step can bring down a production environment, that shift from "click and hope" to "code and verify" is not a convenience — it is a professional necessity.

This guide is designed for engineers who have never written a line of HCL (HashiCorp Configuration Language). By the end, you will have installed Terraform, written your first configuration, created real cloud resources, and understood the core concepts that separate production-grade Terraform from tutorial-level snippets. Set aside thirty minutes, open your terminal, and follow along.


What Is Infrastructure as Code and Why Should You Care?

Infrastructure as Code (IaC) is the practice of managing infrastructure through machine-readable definition files rather than manual processes. Instead of logging into the AWS Console to create an S3 bucket, you write a configuration file that says "create an S3 bucket with these settings" and let a tool execute it.

Three things make IaC valuable in practice:

Repeatability. A Terraform configuration that builds a staging environment can build an identical production environment. Manual processes accumulate drift — the staging environment slowly diverges from production because someone forgot to replicate a setting change. IaC eliminates that class of problem entirely.

Auditability. When infrastructure lives in code, every change flows through version control. You can trace exactly who changed a firewall rule, when they changed it, what it looked like before, and why they changed it (from the commit message). Try reconstructing that from AWS CloudTrail logs three months after the fact.

Speed. Rebuilding a three-tier application stack manually takes hours of careful clicking. Rebuilding it from Terraform takes minutes. When disaster recovery timelines matter — and they always matter — that difference is existential.

If you want to understand how Terraform fits into the broader DevOps toolchain alongside CI/CD systems, the CI/CD Pipeline Best Practices article covers how teams integrate infrastructure changes into their deployment pipelines.


How Does Terraform Work Under the Hood?

Terraform follows a declarative model. You declare the desired end state of your infrastructure, and Terraform determines what actions to take to reach that state. This differs from imperative tools where you script individual steps ("create this, then configure that, then attach this").

The workflow has three phases:

  1. Init — Terraform downloads the provider plugins for the cloud platforms in your configuration. Providers are how Terraform knows how to talk to AWS, Azure, GCP, Cloudflare, or any of the 4,000+ supported services.

  2. Plan — Terraform compares your configuration files against its stored state (a record of what it previously created) and produces an execution plan showing exactly what it will create, modify, or destroy. Nothing changes yet.

  3. Apply — Terraform executes the plan. Resources are created in dependency order. A VPC is created before the subnet inside it, which is created before the EC2 instance inside that subnet. Terraform handles this dependency graph automatically.

This three-phase workflow is what makes Terraform safe for production. You always see what will happen before it happens.


How Do You Install Terraform?

Installation takes under two minutes on any operating system.

macOS (Homebrew)

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Windows (Chocolatey)

choco install terraform

Windows (Manual)

Download the zip from hashicorp.com/terraform, extract the binary, and add it to your PATH.

Linux (Ubuntu/Debian)

wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

Verify

terraform version
# Terraform v1.10.x

You also need credentials for your cloud provider. For AWS, install the AWS CLI and run aws configure with your access key and secret key. For Azure, use az login. For GCP, use gcloud auth application-default login. Terraform reads these credentials automatically.


What Does Terraform Code Look Like?

Terraform uses HCL (HashiCorp Configuration Language), a domain-specific language designed specifically for infrastructure configuration. HCL reads like structured English. If you can read a JSON file, you can read HCL.

Here is the anatomy of a Terraform configuration:

# Provider block: which cloud, which region
provider "aws" {
  region = "us-east-1"
}

# Resource block: what to create
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-terraform-demo-bucket-2026"

  tags = {
    Environment = "dev"
    ManagedBy   = "terraform"
  }
}

Every resource block has three parts: - Type (aws_s3_bucket) — the kind of resource - Name (my_bucket) — your local label for referencing it elsewhere - Arguments — the configuration settings inside the braces

The combination of type and name must be unique within your project. You reference resources elsewhere using the pattern aws_s3_bucket.my_bucket.id or aws_s3_bucket.my_bucket.arn.


Your First Terraform Project: A Complete Walkthrough

Let us build a real project from scratch. You will create an S3 bucket with security best practices and an EC2 instance running a web server — all managed by Terraform.

Step 1: Create the Project Structure

mkdir terraform-first-project
cd terraform-first-project

Create three files: main.tf, variables.tf, and outputs.tf. This is the standard Terraform file layout that every team uses.

Step 2: Define Providers and Backend (main.tf)

terraform {
  required_version = ">= 1.9.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.50"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.6"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

The ~> 5.50 version constraint means "any version from 5.50 up to but not including 6.0." This protects you from breaking changes in major versions while still receiving patches and minor features.

Step 3: Declare Variables (variables.tf)

variable "aws_region" {
  description = "AWS region for resource deployment"
  type        = string
  default     = "us-east-1"
}

variable "project_name" {
  description = "Project name used in resource naming and tags"
  type        = string
  default     = "my-first-project"
}

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

variable "instance_type" {
  description = "EC2 instance size"
  type        = string
  default     = "t3.micro"
}

Variables make your configuration reusable. The same code can create a t3.micro in dev and a t3.medium in production by changing one value.

Step 4: Create an S3 Bucket with Security Defaults (main.tf continued)

resource "random_id" "bucket_suffix" {
  byte_length = 4
}

resource "aws_s3_bucket" "app_data" {
  bucket = "${var.project_name}-${var.environment}-${random_id.bucket_suffix.hex}"

  tags = {
    Name        = "${var.project_name}-storage"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_s3_bucket_versioning" "app_data" {
  bucket = aws_s3_bucket.app_data.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "app_data" {
  bucket = aws_s3_bucket.app_data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
    bucket_key_enabled = true
  }
}

resource "aws_s3_bucket_public_access_block" "app_data" {
  bucket = aws_s3_bucket.app_data.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Notice how this configuration does four things that manual creation often skips: it enables versioning (so you can recover deleted objects), enforces encryption at rest, blocks all public access paths, and tags the resource for cost tracking. These are not optional extras. They are baseline security.

Step 5: Launch an EC2 Instance (main.tf continued)

data "aws_vpc" "default" {
  default = true
}

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }

  filter {
    name   = "state"
    values = ["available"]
  }
}

resource "aws_security_group" "web" {
  name        = "${var.project_name}-${var.environment}-web-sg"
  description = "Allow HTTP and HTTPS inbound traffic"
  vpc_id      = data.aws_vpc.default.id

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

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    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"]
  }

  tags = {
    Name        = "${var.project_name}-web-sg"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_instance" "web" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = var.instance_type
  vpc_security_group_ids = [aws_security_group.web.id]

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  metadata_options {
    http_tokens = "required"
  }

  user_data = <<-EOF
    #!/bin/bash
    dnf update -y
    dnf install -y nginx
    systemctl start nginx
    systemctl enable nginx
    echo "<h1>Deployed with Terraform</h1>" > /usr/share/nginx/html/index.html
  EOF

  tags = {
    Name        = "${var.project_name}-${var.environment}-web"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

The data blocks are data sources — they read information from AWS without creating anything. Here we look up the default VPC and the latest Amazon Linux 2023 AMI. This means your configuration always uses the current AMI without hardcoding an ID that becomes stale.

The metadata_options block enforces IMDSv2, which prevents a class of SSRF attacks against the instance metadata service. This is the kind of security default that separates production Terraform from tutorial Terraform.

Step 6: Define Outputs (outputs.tf)

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

output "s3_bucket_name" {
  description = "Name of the S3 bucket"
  value       = aws_s3_bucket.app_data.bucket
}

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

Outputs display useful information after terraform apply completes. They also let other Terraform configurations reference these values through remote state.

Step 7: Run the Workflow

# Download provider plugins
terraform init

# Preview what Terraform will create
terraform plan

# Create the resources (type "yes" when prompted)
terraform apply

# When you are done, tear everything down
terraform destroy

The terraform plan output shows a diff of every resource that will be created, modified, or destroyed. Read this output carefully before running apply — especially in production environments. This plan-then-apply discipline is the single most important Terraform practice.


What Is Terraform State and Why Does It Matter?

When Terraform creates resources, it records their real-world identifiers (AWS resource IDs, IP addresses, ARNs) in a state file called terraform.tfstate. This file is how Terraform maps your configuration to the actual infrastructure it created.

State is critical. Without it, Terraform would not know that the aws_instance.web in your code corresponds to instance i-0abc123def456 in AWS. It would try to create a duplicate every time you run apply.

For solo projects and learning, the default local state file works fine. For team environments, local state creates a serious problem: two engineers running terraform apply at the same time will corrupt the state file and potentially create duplicate or conflicting resources.

The solution is remote state with locking. The standard AWS pattern stores state in S3 (with versioning for recovery) and uses a DynamoDB table for locking (so only one apply can run at a time):

terraform {
  backend "s3" {
    bucket         = "my-org-terraform-state"
    key            = "projects/my-first-project/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-state-locks"
    encrypt        = true
  }
}

After adding a backend, run terraform init again. Terraform will offer to migrate your local state to the remote backend.


How Does Terraform Compare to Other IaC Tools?

This is the question every beginner asks, and the answer depends on your team and your constraints. Here is an honest comparison based on production use.

Criteria Terraform CloudFormation Pulumi AWS CDK
Language HCL (domain-specific) JSON / YAML Python, TypeScript, Go, C#, Java TypeScript, Python, Java, C#, Go
Multi-cloud Yes (4,000+ providers) AWS only Yes (100+ providers) AWS only (synthesizes to CloudFormation)
State management You manage (S3, Terraform Cloud, etc.) AWS manages automatically You manage (Pulumi Cloud, S3, etc.) AWS manages (via CloudFormation)
Learning curve Medium — HCL is simple but unique Low if you know AWS Low if you already write application code Medium — CDK constructs add abstraction
Community modules 100,000+ in Terraform Registry CloudFormation Resource Types 10,000+ packages AWS Construct Library
Drift detection Manual (terraform plan) Built-in since 2022 Manual (pulumi preview) Via CloudFormation
Testing terraform test (native), Terratest cfn-lint, TaskCat Native unit tests in your language CDK assertions library
License BSL 1.1 (OpenTofu fork is MPL 2.0) Proprietary (free) Apache 2.0 (engine) Apache 2.0
Best for Multi-cloud teams, large ecosystems AWS-only teams, AWS-native workflows Teams that prefer general-purpose languages AWS teams that want typed abstractions

Pick Terraform if you work across multiple clouds, want the largest ecosystem of providers and modules, or are joining a team that already uses it. Most job postings that mention IaC specify Terraform by name.

Pick CloudFormation if you operate exclusively on AWS, need deep integration with AWS services like Service Catalog and StackSets, and want AWS to manage state for you.

Pick Pulumi if your team writes TypeScript or Python and wants to use familiar testing tools and IDE features instead of learning a new language.

Pick CDK if you want typed abstractions over CloudFormation with the safety net of CloudFormation's built-in state management and rollback.

For a detailed technical comparison with code examples in each tool, read the full Terraform vs CloudFormation vs Pulumi breakdown.


What Are the Most Common Terraform Mistakes Beginners Make?

Not pinning provider versions. Without a version constraint, terraform init downloads the latest provider version. A major version update can introduce breaking changes that fail your next apply. Always use ~> constraints.

Committing state files to Git. The terraform.tfstate file contains resource IDs and sometimes sensitive values. Add *.tfstate and *.tfstate.backup to your .gitignore immediately.

Storing secrets in .tfvars files. Database passwords, API keys, and tokens should never live in files committed to version control. Use environment variables prefixed with TF_VAR_ or integrate a secrets manager like AWS Secrets Manager or HashiCorp Vault.

Making manual changes to Terraform-managed resources. If you modify a security group in the AWS Console, the next terraform plan will show a change to revert your manual edit. Terraform is the source of truth. Changing resources outside of Terraform creates drift that confuses the team and causes outages.

Skipping terraform plan before apply. In CI/CD pipelines, always run plan as a separate step and review the output before apply executes. Automated applies without review are how teams accidentally delete production databases.

Hardcoding AMI IDs. AMIs are region-specific and become outdated. Use a data source to look up the latest AMI dynamically, as shown in the walkthrough above.


What Should You Learn After the Basics?

Once you can write, plan, and apply configurations confidently, the next steps are:

Modules. Modules package reusable infrastructure patterns. Instead of copying your S3 bucket configuration into every project, you write a module once and reference it with different parameters. The Terraform Registry has thousands of community-maintained modules for VPCs, EKS clusters, RDS instances, and more.

Workspaces. Workspaces let you manage multiple environments (dev, staging, prod) from the same configuration, each with its own isolated state. They work well for small teams, though larger organizations typically use separate configurations per environment.

Remote state data sources. The terraform_remote_state data source lets one Terraform configuration read outputs from another. Your networking configuration can export VPC and subnet IDs that your application configuration consumes.

Policy as Code. HashiCorp Sentinel and Open Policy Agent (OPA) let you write automated rules that enforce governance — requiring encryption on all storage, tagging on all resources, or restricting instance types to approved sizes.

CI/CD integration. Production teams run Terraform through CI/CD pipelines, not from laptops. Tools like Atlantis, Spacelift, and Terraform Cloud automate the plan-review-apply workflow with pull request comments showing the plan output.

The DevOps Pipelines collection at Citadel Cloud includes structured learning paths that take you from these basics through advanced patterns like multi-account architectures and GitOps-driven infrastructure workflows.


Frequently Asked Questions

Do I need to know AWS before learning Terraform?

You need to understand the services you are provisioning at a conceptual level. You should know what an S3 bucket is, what an EC2 instance does, and what a security group controls before writing Terraform code that creates them. However, you do not need deep AWS expertise first. Many engineers learn AWS and Terraform in parallel — writing Terraform forces you to understand AWS resources more precisely than clicking through the console does, because you must specify every configuration option explicitly. Start with four core services: VPC, EC2, S3, and IAM. Those cover the majority of what you will write in your first few months.

Is Terraform free to use?

The Terraform CLI is free and open-source under the Business Source License (BSL 1.1). You can download it, write configurations, and manage infrastructure without paying HashiCorp anything. HashiCorp also offers Terraform Cloud, which has a free tier for small teams (up to 500 managed resources) and paid tiers for larger organizations that need features like policy enforcement, private module registries, and team access controls. The open-source fork OpenTofu, maintained by the Linux Foundation under the MPL 2.0 license, is a fully compatible alternative if the BSL license is a concern for your organization. The HCL syntax, provider ecosystem, and workflow are identical between Terraform and OpenTofu.

How does Terraform handle resources that someone changed manually?

Terraform detects manual changes through a process called drift detection. When you run terraform plan, Terraform queries your cloud provider's API to check the current state of each resource and compares it to the stored state file. If someone changed a security group rule through the AWS Console, terraform plan will show that change and propose reverting it to match your configuration. You can then decide to update your configuration to match the new reality or run terraform apply to enforce the original configuration. For critical resources, use the lifecycle { prevent_destroy = true } argument to block accidental deletions.

What is the difference between Terraform and Ansible?

Terraform and Ansible solve different problems. Terraform provisions infrastructure — it creates and configures cloud resources like servers, networks, storage, and databases. Ansible configures what runs inside those resources — it installs software, manages configuration files, deploys applications, and runs administrative tasks on existing servers. In a typical DevOps workflow, Terraform creates the EC2 instance and Ansible configures the software running on it. Some teams use Terraform's user_data scripts for simple server configuration (as shown in this guide), but for anything beyond basic setup, a dedicated configuration management tool is more maintainable.

Should I learn Terraform or CloudFormation in 2026?

For career flexibility, learn Terraform first. It works across every major cloud provider, and the majority of DevOps and cloud engineering job listings specify Terraform as a required or preferred skill. CloudFormation is worth learning if you work exclusively on AWS and your organization has an existing CloudFormation investment. The concepts transfer between tools — understanding declarative infrastructure, state management, and resource dependencies in one tool makes learning the other straightforward. For a detailed side-by-side comparison with code examples, read Terraform vs CloudFormation vs Pulumi.


Key Takeaway

Terraform turns your infrastructure into version-controlled, repeatable code — learn the init-plan-apply workflow, pin your versions, manage your state remotely, and you have a foundation that scales from a single side project to a multi-cloud enterprise.


Start Building with Terraform Today

If you are ready to go deeper than this 30-minute introduction, the free DevOps and Terraform courses at Citadel Cloud walk you through multi-account architectures, module design patterns, CI/CD pipeline integration with GitHub Actions, and policy-as-code enforcement with OPA. The courses include hands-on labs with real AWS resources and are designed for the practitioner who wants to move from tutorial-level knowledge to production confidence.

The DevOps Pipelines collection also includes downloadable Terraform module templates, CI/CD workflow configurations, and architecture diagrams that you can adapt for your own infrastructure projects.


Kenny Ogunlowo is a Senior Multi-Cloud DevSecOps Architect with enterprise experience at Cigna Healthcare, Lockheed Martin, NantHealth, BP Refinery, and Patterson UTI. He holds AWS, Azure, and GCP certifications with specializations in FedRAMP, CMMC, and HIPAA compliance environments.

You might also like