Terraform Basics for Infrastructure as Code
Learn how Terraform provisions cloud infrastructure declaratively, from your first plan and apply to remote state, modules, and safe team workflows.
What it is and when to use it
Terraform is an infrastructure as code tool from HashiCorp that lets you describe cloud and SaaS resources in declarative configuration files. You state the desired end result, and Terraform works out which API calls are needed to get there. It supports AWS, Azure, Google Cloud, Kubernetes, Cloudflare, GitHub and thousands of other providers through plugins.
Use Terraform when you need repeatable, reviewable infrastructure: networks, databases, clusters, DNS records, IAM roles. It is a poor fit for configuring software inside a server, which is better handled by tools such as Ansible or by container images. OpenTofu is a community fork that uses nearly identical syntax and commands.
Quick start
Install the CLI, then create a directory with a file named main.tf.
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-1"
}
resource "aws_s3_bucket" "assets" {
bucket = "stackbook-demo-assets-12345"
}
output "bucket_arn" {
value = aws_s3_bucket.assets.arn
}
Then run the core workflow.
terraform init # download providers, configure backend
terraform fmt # normalize formatting
terraform validate # check syntax and references
terraform plan -out=tfplan
terraform apply tfplan
terraform destroy # remove everything when finished
Core concepts
Providers are plugins that talk to an API. Pin their versions so upgrades are deliberate.
Resources are the objects Terraform manages. Each has a type, a local name, and arguments. References such as aws_s3_bucket.assets.arn create implicit dependencies, so Terraform orders operations correctly.
Variables and outputs make configuration reusable.
variable "environment" {
type = string
description = "Deployment environment"
default = "dev"
}
locals {
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
Values can be supplied with a terraform.tfvars file, the -var flag, or TF_VAR_ environment variables.
State is a file that maps your configuration to real resources. Terraform compares state, configuration and the live API to build a plan. By default state is local, which is fine for experiments but unsafe for teams. Use a remote backend with locking.
terraform {
backend "s3" {
bucket = "my-tf-state"
key = "prod/network.tfstate"
region = "eu-west-1"
dynamodb_table = "tf-locks"
encrypt = true
}
}
Modules package a group of resources behind inputs and outputs. Call one with a module block and a source argument pointing at a local path, Git repo or registry address.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.8.1"
name = "main"
cidr = "10.0.0.0/16"
}
Data sources read existing information, such as the latest AMI, without managing it. Use for_each or count to create multiple similar resources, preferring for_each because it keys resources by name rather than position.
Useful commands for day two
terraform state list
terraform state show aws_s3_bucket.assets
terraform import aws_s3_bucket.assets existing-bucket-name
terraform workspace new staging
terraform output -json
Since version 1.5 you can also write import blocks in configuration, which makes adoption of existing resources reviewable in a plan.
Pitfalls
- Never commit state files or tfvars containing secrets. State often holds sensitive values in plain text, so encrypt the backend and restrict access.
- Do not edit resources by hand in the console. Drift causes surprising plans. Run terraform plan on a schedule to detect it.
- Always read the plan. Look for lines marked as destroy or replace, which can mean data loss for databases.
- Avoid giant single states. Split by lifecycle and blast radius, for example network, data and application layers.
- Use lifecycle blocks such as prevent_destroy for critical resources, and avoid count with lists that can reorder.
- Pin provider and module versions and commit the .terraform.lock.hcl file.
How it fits an architecture
Terraform typically sits at the base of the delivery stack. A pipeline in GitHub Actions runs plan on pull requests, posts the output for review, and applies on merge to main using short-lived cloud credentials. Terraform creates the cluster, networks and databases; Helm or Argo CD then deploy applications onto that cluster; Ansible, if used, configures any long-lived virtual machines. Outputs from one state can feed another through remote state data sources or a secrets store.
Key takeaways
- Terraform is declarative: describe the target, review the plan, then apply.
- Remote state with locking is mandatory for any team.
- Modules and pinned versions keep configuration consistent and safe to upgrade.
- Treat plan output as a code review artifact, especially destroy and replace actions.
- Keep states small and separated by blast radius, and run it from CI rather than laptops.