Terraform for Cloud Engineers: The Skills That Matter at Work

Terraform is on almost every cloud engineering job description, but most learning resources focus on passing exams or grinding tutorials. This page covers what you actually need to know to work with Terraform on a real team — the patterns you write, the mistakes that will bite you, and what a code review looks like in practice.

What you write in HCL every day

HashiCorp Configuration Language (HCL) is the language Terraform uses. Once you are past the basics, the things you write most often at work are resources, data sources, variables, outputs, and locals. Knowing when to use each one matters more than memorising syntax.

A resource creates or manages infrastructure. A data source reads existing infrastructure without managing it. The distinction is important: if you declare an S3 bucket as a resource, Terraform owns it. If you use a data source to reference a bucket someone else created, Terraform just looks it up. Confusing the two causes real problems.

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

# Data source — Terraform reads this but does not manage it
data "aws_vpc" "main" {
  tags = {
    Name = "production-vpc"
  }
}

Locals are for computed values you want to reuse inside a module without exposing them as input variables. They reduce repetition and make complex expressions readable.

locals {
  name_prefix = "${var.project}-${var.environment}"
  common_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

State file: what goes wrong and why

The Terraform state file is how Terraform tracks what it has created. It maps your HCL configuration to real cloud resources. If the state file is wrong or missing, Terraform does not know what exists — and it will try to create things that already exist, or delete things it thinks are no longer needed.

The most important things to know about state at work:

  • Never store state locally in a team. The state file belongs in remote storage — an S3 bucket, GCS bucket, or Terraform Cloud. If two people run terraform apply at the same time against a local state file, you get corruption.
  • State locking prevents concurrent runs. Remote backends like S3+DynamoDB or GCS implement locking. If you see a “state is locked” error, someone else is running Terraform against the same workspace.
  • Do not edit the state file manually. Use terraform state mv, terraform state rm, and terraform import for surgical changes.

A typical remote state backend for AWS looks like this:

terraform {
  backend "s3" {
    bucket         = "my-company-terraform-state"
    key            = "production/app/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}

Common mistake: Using the same state key for multiple environments. If your dev and production Terraform runs share a state file, a mistake in dev can destroy production. Use separate state keys or separate backends per environment.

Module patterns you will encounter

Modules are how Terraform code is shared and reused. At work you will use three kinds: the root module (your main configuration), local modules (shared code inside the same repo), and remote modules (from a registry or Git repository).

The most important thing to understand about modules is the boundary they create. Variables are inputs, outputs are outputs, and everything else is internal. When you call a module, you only know about its interface — not how it works inside.

module "app_bucket" {
  source      = "./modules/s3-bucket"
  bucket_name = "my-app-${var.environment}"
  versioning  = true
  tags        = local.common_tags
}

output "bucket_arn" {
  value = module.app_bucket.arn
}

In most teams you will encounter registry modules — published modules from the Terraform Registry or a private registry. The usage pattern is the same, but the source string points to a remote location.

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.0"

  name = "production-vpc"
  cidr = "10.0.0.0/16"
  azs  = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}

Mistake to avoid: Pinning modules to source = ”…” version = ”>= 1.0” with a loose version constraint. This means a module upgrade can happen silently on the next terraform init and change behaviour you did not expect. Use exact or pessimistic constraint versions (~> 5.1) in production.

What a Terraform PR review looks like

On most cloud engineering teams, Terraform changes go through a pull request process. The typical flow is: branch → write HCL → run terraform plan → post the plan output to the PR → get a review → merge → apply.

As a reviewer, you are looking at a few things:

  • Does the plan match the stated intent of the PR? If the PR says “add an S3 bucket” but the plan shows a security group being changed, something is wrong.
  • Are there any unexpected destroy operations? A line like - destroy in the plan should always prompt a question.
  • Are tags, naming conventions, and variable naming consistent with the rest of the codebase?
  • Is sensitive data (passwords, keys) handled correctly — ideally via a secrets manager rather than being hardcoded?

As the author, the most useful thing you can do is make the plan readable. Break large changes into small PRs. Write a clear description of what is changing and why. If you are making a change that looks destructive but is safe (e.g. renaming a resource using terraform state mv), explain that explicitly.

Many teams use tools like Atlantis or Terraform Cloud to automate the plan step — the plan runs automatically on every PR and posts the output as a comment. If your team uses this, get familiar with how to re-trigger plans when they go stale.

Common errors and how to read them

Terraform errors look intimidating at first but follow predictable patterns. Here are the ones you will see most often:

Error patternWhat it usually means
Error: Reference to undeclared resourceYou referenced a resource that does not exist in the config — check spelling and whether it is in scope
Error: Cycle: A depends on B depends on ATwo resources reference each other creating a loop — you need to break the dependency
Error: Error acquiring the state lockAnother Terraform process is running against this workspace — wait or force-unlock if safe
Error: Provider configuration not presentThe required provider block is missing or the credentials are not set
Error: 403 ForbiddenYour credentials do not have permission to create the resource

The most useful habit: when Terraform throws an error, read the full error output from top to bottom. The first line tells you the type of error. The last few lines usually tell you the file and line number where Terraform got confused.

What to put on your CV and portfolio

Most CVs list “Terraform” in the skills section and say nothing else. That tells a hiring manager you have heard of Terraform. To stand out, give evidence.

Strong CV bullets describe what you built with Terraform, at what scale, and what problem it solved:

  • Weak: “Experience with Terraform”
  • Stronger: “Wrote Terraform modules to provision VPC, EKS cluster, and RDS instances across dev and production environments; managed remote state in S3 with DynamoDB locking”

For a portfolio project, a good Terraform project to build and share on GitHub is a complete, working cloud environment: a VPC with subnets, a compute resource (EC2 or EKS), a database, IAM roles, and an S3 bucket for state. Keep it small but make it correct — remote state, proper variable handling, no hardcoded credentials, tagged resources.

If you have a GitHub repository, make sure your Terraform code has a clear README explaining what the infrastructure does and how to deploy it. Reviewers do look at repos linked from CVs.

The daily Terraform workflow

The commands you will run most often are straightforward once you have used them a few times:

# Initialise — run this after any provider or backend change
terraform init

# Format your code consistently
terraform fmt -recursive

# Validate syntax and basic logic
terraform validate

# Preview what will change — always run this before apply
terraform plan -out=tfplan

# Apply the plan
terraform apply tfplan

# Inspect current state
terraform state list
terraform state show aws_instance.web

Career insight: The engineers who are trusted to apply Terraform in production are the ones who always plan before applying, review the plan carefully, and never run terraform apply interactively when they could run it from a plan file. The habit signals discipline, which teams value.