Learn how Terraform works: providers, resources, state, plan and apply, with a working AWS example, remote state and common mistakes.
In this article
Infrastructure as code (IaC) means describing your servers, networks, databases and permissions in text files, then letting a tool create and update them to match. Terraform is the most widely used tool for this. Instead of clicking through a cloud console and hoping you remember what you did, you write a configuration, review it like any other code change, and apply it. The result is infrastructure you can reproduce, audit, roll back and hand to a colleague.
This guide explains how Terraform works, walks through a small but realistic example, and covers the habits that stop a first project turning into a mess. It assumes no prior Terraform knowledge.
Why bother with infrastructure as code
Hand-built infrastructure has a predictable failure pattern. A system is set up under pressure, a few settings are tweaked later, and a year on nobody knows exactly how production differs from staging. Recreating it after a disaster becomes guesswork. IaC addresses this directly:
- Repeatability. The same code creates the same environment, whether it is for development, testing or production.
- Review. Changes go through pull requests, so a second pair of eyes sees a firewall change before it happens.
- History. Version control records who changed what and why.
- Recovery. If an environment is lost, you rebuild it from code. This is central to disaster recovery in the cloud.
- Cost control. Temporary environments can be created and destroyed on demand, which helps with cloud spend.
How Terraform works
Terraform uses a declarative language called HCL. You describe the end state you want, not the steps to reach it. The core concepts are few:
- Providers are plugins that talk to a platform: AWS, Azure, Cloudflare, GitHub and many more.
- Resources are the things you want to exist, such as a storage bucket or a virtual network.
- Variables make a configuration reusable by parameterising values like environment name or region.
- Outputs expose useful values, such as an address, after creation.
- State is a record of what Terraform has created, which it compares against your code to decide what to change.
- Modules are reusable bundles of resources, so a pattern can be written once and used many times.
The daily workflow has four commands:
terraform initdownloads providers and prepares the working directory.terraform fmtandterraform validatetidy and check the code.terraform planshows exactly what would be created, changed or destroyed, without doing it.terraform applycarries out the plan after you confirm.
The plan step is the heart of the safety model. Read it every time, especially lines marked for destruction or replacement.
A small example
This configuration creates a private, versioned storage bucket on AWS. The bucket name must be globally unique, so it takes a variable.
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
variable "region" {
type = string
default = "eu-west-1"
}
variable "bucket_name" {
type = string
description = "Globally unique bucket name"
}
resource "aws_s3_bucket" "assets" {
bucket = var.bucket_name
tags = {
Environment = "dev"
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_versioning" "assets" {
bucket = aws_s3_bucket.assets.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "assets" {
bucket = aws_s3_bucket.assets.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
output "bucket_arn" {
value = aws_s3_bucket.assets.arn
}
Run it like this:
terraform init
terraform plan -var="bucket_name=example-assets-0001"
terraform apply -var="bucket_name=example-assets-0001"
Notice that the versioning and public-access settings are separate resources that refer to the bucket by reference. Terraform builds a dependency graph from those references, so it creates things in the right order automatically.
State: the part that bites
State is a file mapping your code to real resources. By default it is stored locally, which is fine for experiments and dangerous for teams. If it is lost, Terraform no longer knows what it manages; if two people apply at once, state can be corrupted. For any shared work, use a remote backend with locking. On AWS a common setup is an S3 bucket for state with locking enabled; Azure Storage and Terraform's own hosted services are alternatives.
terraform {
backend "s3" {
bucket = "example-terraform-state"
key = "network/terraform.tfstate"
region = "eu-west-1"
use_lockfile = true
encrypt = true
}
}
State can contain sensitive values in plain text, so restrict access to the state bucket tightly, enable encryption and versioning, and never commit state files to Git. Check your Terraform version's documentation for the current locking options, since backend features evolve.
Structuring a real project
- Separate environments. Keep development, staging and production in separate state files, and ideally separate cloud accounts or subscriptions.
- Use modules sparingly at first. Copying a pattern twice is fine. Extract a module when the third use makes the shape obvious.
- Pin versions. Pin Terraform and provider versions so a surprise upgrade does not change your plan.
- Tag everything. Environment, owner and purpose tags make cost reporting and clean-up possible.
- Run it in a pipeline. Plan on every pull request, apply on merge, with approval for production. See our GitHub Actions guide for the pipeline mechanics.
Terraform and its alternatives
CloudWiss lists both Terraform and CloudFormation among its infrastructure-as-code tools. CloudFormation is AWS-only and tightly integrated with it; Azure has its own native language, Bicep. Terraform works across providers with one syntax, which is attractive if you use more than one platform or may do so later, as discussed in AWS versus Azure. The best tool is the one your team will actually keep using consistently, so the choice matters less than the discipline.
Common mistakes
- Editing resources in the console afterwards. This creates drift between code and reality. If an emergency change is unavoidable, record it and bring the code back into line.
- Keeping state locally in a team. Use a remote, locked backend from the start.
- Committing secrets. Never put passwords or keys in
.tfor.tfvarsfiles that go to Git. Reference a secret manager. - Applying without reading the plan. A rename can show up as destroy-and-create, which deletes a database.
- One enormous configuration. A single state for everything makes every change slow and risky. Split by lifecycle, such as network, data and applications.
- Not protecting critical resources. Use
lifecycle { prevent_destroy = true }on things like production databases, and enable deletion protection at the provider level too. - Ignoring drift. Run a scheduled plan to detect changes made outside Terraform.
- Importing never. Existing hand-built resources can be brought under management with
terraform importor import blocks; you do not have to start from nothing.
A getting-started checklist
- Choose one small, low-risk area, such as a storage bucket or a test network.
- Write it in Terraform, with variables and tags, in a Git repository.
- Configure a remote backend with locking and encryption.
- Add
fmt,validateandplanto a pull-request check. - Apply through a pipeline with a restricted role, not a personal admin account.
- Document how to bootstrap the state bucket and the pipeline itself.
- Expand gradually, importing existing resources as you go.
Summary
Terraform turns infrastructure into reviewable, repeatable code. Start small, protect your state, read every plan and let a pipeline do the applying. If you would rather have an experienced team set the foundations, CloudWiss's cloud and DevOps services include infrastructure as code with Terraform and CloudFormation, together with the pipelines, monitoring and documentation around it.
Share this article
General educational information, not legal or professional advice for your specific situation.
