DevOps Guide February 2026

Infrastructure as Code with Terraform: A Beginner's Walkthrough

Managing servers, networks, and cloud services by clicking through web consoles is slow, error-prone, and impossible to audit. Infrastructure as Code (IaC) replaces manual provisioning with machine-readable configuration files that can be versioned, reviewed, tested, and reused โ€” exactly like application code. Terraform, created by HashiCorp, has become the industry-standard tool for declarative, cloud-agnostic infrastructure management. This guide walks you through everything you need to go from zero to your first Terraform deployment.

What is Infrastructure as Code?

Infrastructure as Code is the practice of managing and provisioning computing infrastructure through code rather than through manual processes. Instead of logging into a cloud console, clicking buttons, and filling in forms, you write configuration files that describe the desired state of your infrastructure. A tool then reads those files and makes the real-world infrastructure match the description.

There are two fundamental approaches to IaC. The declarative approach describes what the final state should look like, and the tool figures out the steps to get there. The imperative approach describes the specific steps to execute in order. Terraform uses the declarative model โ€” you describe the infrastructure you want, and Terraform calculates the changes needed to reach that state. This makes configurations easier to reason about, because you focus on the outcome rather than the procedure.

The benefits of IaC are substantial and well-documented. Teams that adopt IaC report dramatically fewer configuration errors, faster provisioning times, and the ability to reproduce entire environments in minutes rather than days. Because infrastructure definitions live in version control alongside application code, every change is tracked, reviewable, and reversible. This eliminates the "snowflake server" problem where no two environments are quite the same.

3,000+
Terraform Providers
90%
Fewer Config Errors
$1.2B
HashiCorp Valuation
40%
Faster Provisioning

Terraform vs Other IaC Tools

Terraform is not the only IaC tool available, and choosing the right one depends on your cloud strategy, team skills, and specific requirements. The four most widely used tools each have distinct strengths and trade-offs. Understanding these differences helps you make an informed decision โ€” or recognise when a combination of tools is the best approach.

The table below compares the major IaC tools across the dimensions that matter most in practice: cloud support, configuration language, approach, and organisational fit.

IaC Tool Comparison

Tool Cloud Support Language Approach Maintained By
TerraformMulti-cloud (AWS, Azure, GCP, and 3,000+ providers)HCL (HashiCorp Configuration Language)DeclarativeHashiCorp
AWS CloudFormationAWS onlyJSON / YAMLDeclarativeAWS
AnsibleMulti-platform (cloud, on-premises, network devices)YAMLImperative / DeclarativeRed Hat
PulumiMulti-cloud (AWS, Azure, GCP, Kubernetes)General-purpose languages (Python, TypeScript, Go, C#)DeclarativePulumi

Source: Official documentation from HashiCorp, AWS, Red Hat, and Pulumi (2025)

Terraform excels when you need to manage infrastructure across multiple cloud providers or want a single, consistent workflow regardless of where your resources live. CloudFormation is the natural choice for teams fully committed to AWS, offering deep integration with every AWS service. Ansible is strongest for configuration management and server provisioning, often used alongside Terraform rather than as a replacement. Pulumi appeals to teams that prefer writing infrastructure definitions in the same languages they use for application code.

Terraform's Cloud-Agnostic Advantage

The single most compelling reason teams choose Terraform is its cloud-agnostic provider model. A single Terraform codebase can provision an AWS VPC, an Azure database, a Cloudflare DNS record, and a Datadog monitoring dashboard. This is not theoretical โ€” it is how modern multi-cloud architectures are built in practice. The provider ecosystem includes over 3,000 integrations, covering everything from major cloud platforms to SaaS tools, databases, and even physical hardware.

HCL Basics: The HashiCorp Configuration Language

Terraform configurations are written in HCL (HashiCorp Configuration Language), a domain-specific language designed specifically for infrastructure definitions. HCL is not a general-purpose programming language โ€” it was created to be readable by humans and parseable by machines. If you can read JSON or YAML, you can read HCL within minutes.

HCL uses a block-based syntax where each block defines a piece of infrastructure or configuration. The building blocks are straightforward: providers connect you to cloud platforms, resources define the infrastructure components you want to create, variables allow you to parameterise your configurations, and outputs expose values for other tools or modules to consume.

Core HCL Constructs

Construct Description Syntax Example
ProviderConnects Terraform to a cloud platform or service APIprovider "aws" { region = "eu-west-2" }
ResourceDefines an infrastructure component to create and manageresource "aws_instance" "web" { ami = "ami-0c55b159" }
VariableDeclares an input parameter for reusable configurationvariable "region" { default = "eu-west-2" }
OutputExposes a value after resources are createdoutput "ip" { value = aws_instance.web.public_ip }
Data SourceReads information from existing infrastructure without managing itdata "aws_ami" "latest" { most_recent = true }
LocalDefines a computed value for use within the configurationlocals { env_prefix = "${var.env}-app" }
ModuleGroups related resources into a reusable, shareable packagemodule "vpc" { source = "./modules/vpc" }

Source: Terraform Language Documentation โ€” HashiCorp (2025)

HCL supports expressions, functions, and conditional logic that make configurations flexible without requiring a full programming language. You can use string interpolation, for-each loops, conditional expressions, and built-in functions for tasks like formatting strings, calculating CIDR blocks, or encoding data. The language strikes a deliberate balance between power and simplicity โ€” expressive enough for real-world infrastructure, constrained enough to remain auditable.

HCL Is Designed for Readability

HashiCorp deliberately chose to create a new language rather than use JSON, YAML, or a general-purpose language. JSON lacks comments and is verbose. YAML has indentation pitfalls that cause subtle bugs. General-purpose languages like Python or TypeScript are powerful but make configurations harder to review and audit. HCL occupies the sweet spot: it supports comments, is concise, uses a consistent block structure, and can be parsed both by humans scanning a pull request and by automated tools running policy checks.

Your First Terraform Project

The best way to learn Terraform is to build something real. The Terraform workflow follows four core commands that you will use in every project, every day. Understanding this workflow is the foundation of everything else โ€” modules, state management, and CI/CD integration all build on top of these four steps.

The Terraform Workflow

Command What It Does When You Run It
terraform initDownloads provider plugins and initialises the backend for state storageOnce when starting a new project, or after adding a new provider or module
terraform planPreviews the changes Terraform will make โ€” a dry run that shows what will be created, modified, or destroyedBefore every apply, to review and validate the intended changes
terraform applyExecutes the planned changes and creates, updates, or deletes real infrastructure resourcesAfter reviewing the plan and confirming the changes are correct
terraform destroyTears down all infrastructure managed by the current configurationWhen decommissioning an environment or cleaning up after testing

Source: Terraform CLI Documentation โ€” HashiCorp (2025)

A typical Terraform project organises its configuration across several files, each with a specific purpose. While Terraform treats all .tf files in a directory as a single configuration, the convention of separating concerns into distinct files makes projects easier to navigate, review, and maintain as they grow.

Standard Terraform File Structure

File Purpose What It Contains
main.tfPrimary resource definitionsThe core infrastructure resources โ€” instances, databases, networks, load balancers
variables.tfInput variable declarationsAll variable blocks with descriptions, types, defaults, and validation rules
outputs.tfOutput value definitionsValues to expose after apply โ€” IP addresses, DNS names, resource IDs
terraform.tfvarsVariable values for the current environmentConcrete values for variables โ€” region, instance size, environment name
providers.tfProvider configuration and version constraintsProvider blocks, required_providers, and version pinning

Source: Terraform recommended project structure โ€” HashiCorp (2025)

To get started, create a new directory for your project, add a providers.tf file to configure your cloud provider, and a main.tf file to define your first resource. Run terraform init to download the provider plugin, then terraform plan to preview what will be created. Once you are satisfied with the plan, run terraform apply and confirm. Your infrastructure is now real, managed, and reproducible.

Always Review the Plan Before Applying

The terraform plan command is your safety net. It shows you exactly what Terraform intends to create, change, or destroy before any real infrastructure is touched. Never skip this step. In production workflows, the plan output should be reviewed by at least one other team member โ€” just like a code review. Many teams save the plan to a file with terraform plan -out=tfplan and then apply that exact plan with terraform apply tfplan, ensuring that what was reviewed is exactly what gets executed.

State Management

Terraform state is the single most important concept to understand after the basic workflow. When Terraform creates infrastructure, it records the mapping between your configuration and the real-world resources in a state file (terraform.tfstate). This file is how Terraform knows what it manages, what has changed, and what needs to be updated or destroyed.

By default, Terraform stores state locally in the working directory. This works for individual learning and experimentation, but it fails completely in team environments. If two developers run Terraform at the same time with different local state files, they will overwrite each other's changes and corrupt the infrastructure. Remote state backends solve this by storing the state in a shared, lockable location.

State locking is a critical feature that prevents concurrent operations from corrupting your infrastructure. When one team member runs terraform apply, the state is locked so that no one else can make changes until the operation completes. Without state locking, simultaneous applies can result in duplicate resources, orphaned infrastructure, or a state file that no longer reflects reality.

State Backend Options

Backend Best For State Locking Key Considerations
LocalSingle developer, learning, experimentationNoDefault backend. Simple but not suitable for teams. State file lives on your machine only.
S3 + DynamoDBAWS teams, production workloadsYes (via DynamoDB)The most widely used remote backend. S3 stores the state, DynamoDB provides locking. Enable versioning on the S3 bucket for recovery.
Azure Blob StorageAzure teams, production workloadsYes (native)Azure's equivalent of S3. Supports native state locking without a separate locking service.
Terraform CloudManaged solution, small to mid-size teamsYes (built-in)Free tier for up to 5 users. Provides state management, locking, run history, and policy enforcement in a managed service.
GCS (Google Cloud Storage)Google Cloud teams, production workloadsYes (native)Google Cloud's object storage backend. Supports native state locking and versioning for state recovery.

Source: Terraform Backend Configuration Documentation โ€” HashiCorp (2025)

Migrating from local to remote state is straightforward. Add a backend block to your Terraform configuration, run terraform init, and Terraform will prompt you to migrate the existing state to the new backend. This is a one-time operation per project, and it should be done as early as possible โ€” ideally before any production infrastructure is created.

Never Commit State Files to Git

The Terraform state file contains sensitive information including resource IDs, IP addresses, database connection strings, and sometimes passwords or access keys. It should never be committed to version control. Add *.tfstate and *.tfstate.backup to your .gitignore file immediately when starting any Terraform project. Use a remote backend to store state securely, and rely on the backend's encryption and access controls to protect it.

Modules and Best Practices

As your Terraform codebase grows beyond a single project, modules become essential. A module is a self-contained package of Terraform configuration that can be reused across multiple projects, environments, and teams. Instead of copying and pasting resource definitions, you define them once in a module and call that module wherever you need it โ€” with different variables for each environment.

The Terraform community has established a set of best practices that help teams maintain clean, secure, and scalable infrastructure code. These practices are not optional nice-to-haves โ€” they are the difference between a Terraform codebase that scales gracefully and one that becomes an unmaintainable tangle of configuration files.

Terraform Best Practices

Practice Why It Matters How to Implement
Use modules for reusabilityEliminates duplication and ensures consistent infrastructure across environmentsCreate a modules/ directory. Each module has its own main.tf, variables.tf, and outputs.tf. Call modules from root configurations.
Pin provider versionsPrevents breaking changes when providers release new versionsUse required_providers with version constraints: version = "~> 5.0" allows patch updates but blocks major version changes.
Use remote stateEnables team collaboration and prevents state corruption from concurrent operationsConfigure an S3, Azure Blob, GCS, or Terraform Cloud backend. Migrate from local state as early as possible in the project.
Implement state lockingPrevents two team members from applying changes simultaneously and corrupting stateUse a backend that supports locking (DynamoDB for S3, native for Azure/GCS, built-in for Terraform Cloud).
Separate environmentsIsolates dev, staging, and prod to prevent accidental changes to production infrastructureUse separate directories or workspaces for each environment. Each environment has its own state file and variable values.
Use variables for everything configurableMakes configurations reusable and prevents hardcoded values that differ between environmentsDefine variables with types, descriptions, and validation rules. Use .tfvars files for environment-specific values.
Tag all resourcesEnables cost tracking, ownership identification, and automated management across cloud accountsUse a default_tags block in the provider configuration or a local map of standard tags applied to every resource.
Use terraform fmt and terraform validateEnsures consistent formatting and catches syntax errors before they reach a plan or applyRun terraform fmt -recursive and terraform validate in CI pipelines and pre-commit hooks.

Source: Terraform best practices โ€” HashiCorp and community standards (2025)

The Terraform Registry at registry.terraform.io is the official repository for community and partner modules. Before building a module from scratch, check the Registry โ€” there are well-maintained, battle-tested modules for common patterns like VPCs, Kubernetes clusters, databases, and monitoring stacks. Using Registry modules saves time and benefits from the collective experience of thousands of contributors.

Leverage the Terraform Registry

The Terraform Registry hosts thousands of community-maintained modules that codify best practices for common infrastructure patterns. The official AWS VPC module, for example, handles subnet calculations, NAT gateways, route tables, and network ACLs in a single, well-tested module call. Rather than reinventing these patterns, use the Registry as a starting point. Pin module versions just as you pin provider versions, and review the module source code before using it in production to ensure it meets your security and compliance requirements.

Master DevOps Engineering

Infrastructure as Code is a cornerstone of modern DevOps practice. Our accredited DevOps course covers Terraform, CI/CD pipelines, containerisation, monitoring, and the full engineering toolkit you need to build and operate reliable, scalable systems with confidence.

Explore Our DevOps Course