On this page
architecture
Terraform
Declaring cloud resources as code and reviewing the plan before anything changes.
Infrastructure clicked together in a console is infrastructure nobody can explain six months later. Terraform states the desired shape as code, works out the difference from reality, and applies it.
Install
brew install terraform
brew install opentofu # the open-source fork; the command is tofu
The loop
terraform init # download providers, configure the backend
terraform fmt # format
terraform validate # check syntax
terraform plan # preview what will change
terraform apply # apply it
terraform destroy # tear everything down (careful)
Never apply without reading a plan. That single habit prevents most incidents.
A first configuration
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
variable "region" {
type = string
default = "ap-northeast-2"
}
resource "aws_s3_bucket" "assets" {
bucket = "myapp-assets-${var.env}"
tags = {
Environment = var.env
ManagedBy = "terraform"
}
}
output "bucket_name" {
value = aws_s3_bucket.assets.id
}
State
Terraform records what actually exists in terraform.tfstate.
- Never commit it. Secrets can end up in it in plain text.
- On a team, keep it in a remote backend with locking.
terraform {
backend "s3" {
bucket = "myorg-tfstate"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
.gitignore:
*.tfstate
*.tfstate.*
.terraform/
*.tfvars
Separating environments
Splitting by directory is the clearest approach.
infra/
modules/
network/
database/
envs/
dev/
main.tf
terraform.tfvars
prod/
main.tf
terraform.tfvars
Factor shared configuration into modules and vary only the per-environment variables.
module "network" {
source = "../../modules/network"
cidr_block = "10.0.0.0/16"
env = "prod"
}
Working practices
- Attach the plan output to the PR. Reviewers need to see what will change.
- Read every
destroyline carefully. A renamed resource often means a replacement.terraform state mvavoids that. - Pin versions. Without a
~>constraint on providers, your plan output changes on its own one day. - Don’t touch the console. If someone did, the next plan will try to undo it —
either encode the change or
importit.
terraform import aws_s3_bucket.assets myapp-assets-prod
Next
Infrastructure is code; now automate your own work → Automation & Shortcuts
Last updated 19 Aug 2026, 00:00 UTC.