Expert · Infrastructure as code · topic 37 of 50 · 35 min
Terraform for the cloud
Explanation
Why Terraform
CloudFormation only speaks AWS. Many companies use more than one cloud, plus services like GitHub, Cloudflare or Datadog. Terraform uses one language and one workflow for all of them. You write files ending in .tf in HCL (HashiCorp Configuration Language), a simple block-based language. OpenTofu, the open-source fork, reads the same files with the command tofu instead of terraform, so almost everything in this lesson applies to both.
The building blocks
- Provider: a plugin that knows one platform's API, such as hashicorp/aws or hashicorp/azurerm. terraform init downloads it.
- Resource: something Terraform creates and manages, such as aws_s3_bucket. It has a type and a local name.
- Data source: something Terraform only reads, such as the current account ID or an existing VPC. It never creates it.
- Variable: an input, like a parameter, with a type and an optional default.
- Output: a value printed after apply, or read by other configurations.
- Module: a folder of .tf files that you reuse, like a function.
The same versioned bucket in HCL
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0" # pin a major version; check the registry for the current one
}
}
}
provider "aws" {
region = "ap-south-1" # Mumbai
}
variable "env" {
type = string
description = "Environment name: dev or prod"
default = "dev"
}
data "aws_caller_identity" "current" {}
resource "aws_s3_bucket" "results" {
bucket = "results-${var.env}-${data.aws_caller_identity.current.account_id}"
tags = {
env = var.env
}
}
resource "aws_s3_bucket_versioning" "results" {
bucket = aws_s3_bucket.results.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "results" {
bucket = aws_s3_bucket.results.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
output "bucket_name" {
value = aws_s3_bucket.results.bucket
}
output "bucket_arn" {
value = aws_s3_bucket.results.arn
}Read references from left to right: aws_s3_bucket.results.id means "the id attribute of the resource of type aws_s3_bucket named results". Because the versioning resource uses that reference, Terraform knows it must create the bucket first. You rarely need to set the order by hand.
The workflow
terraform init # download providers, set up the backend (run once per folder, and after changes)
terraform fmt # format the files neatly
terraform validate # check syntax and references
terraform plan -out=tfplan # preview the changes and save the plan
terraform apply tfplan # make exactly the saved changes
terraform output bucket_name # read an output
terraform destroy # delete everything in this configuration (asks for confirmation)The plan uses symbols: + create, ~ update in place, - destroy, and -/+ destroy and then create a replacement. It ends with a summary such as "Plan: 3 to add, 0 to change, 0 to destroy." A replacement counts as one add and one destroy. Read every -/+ carefully: replacing a database means a new, empty database unless you planned for it.
The state file
Terraform keeps a state file (terraform.tfstate), a JSON record that maps each resource in your code to the real object in the cloud, such as aws_s3_bucket.results to the bucket results-dev-123456789012. Without it Terraform would not know what it already built. The state can contain secrets in plain text, such as database passwords, so never commit it to Git.
On a team, state must be shared and locked. Shared, so everyone plans against the same truth. Locked, so two people cannot apply at the same moment and corrupt it. A common AWS setup stores state in an S3 bucket with versioning and encryption. Newer Terraform versions can lock using a lock file in the same S3 bucket (use_lockfile); older setups used a DynamoDB table for locking. Check the current backend documentation before you choose.
terraform {
backend "s3" {
bucket = "acme-terraform-state"
key = "results/dev/terraform.tfstate"
region = "ap-south-1"
encrypt = true
use_lockfile = true # S3-native locking in newer versions; older setups used dynamodb_table
}
}Modules
Once the bucket code works, wrap it in a module (a folder such as modules/results-bucket with its own variables and outputs) and call it for each use: module "results" { source = "./modules/results-bucket" env = "prod" }. The public Terraform Registry also has well-tested community modules, for example for a whole VPC. Pin module versions just like provider versions.
Terraform or CloudFormation?
| CloudFormation | Terraform | |
|---|---|---|
| Clouds | AWS only | AWS, Azure, Google Cloud and many other services |
| Language | YAML or JSON | HCL |
| State | Kept for you by AWS in the stack | A state file you store and protect (or a managed service such as HCP Terraform) |
| Preview | Change sets | terraform plan |
| When an apply fails | Automatic rollback to the last good state | No automatic rollback: some changes may be done; fix the code and apply again |
| Licence and cost | Part of AWS, no extra charge for AWS resources | BSL 1.1 since 2023, free for most users; OpenTofu is open source |
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Terraform for the cloud · 1/8
Terraform is a declarative tool that works with almost every cloud through plugins called providers.
Prefer a video by a teacher? Find free videos on “terraform aws tutorial for beginners s3 bucket state backend” on YouTube ↗
In real life: A fintech startup in Bengaluru manages AWS and Cloudflare together
A small payments startup runs its APIs on AWS in Mumbai and its DNS and web firewall on Cloudflare. With Terraform, one pull request can add an S3 bucket, a new API host on AWS and the matching DNS record on Cloudflare. The CI pipeline posts the plan ("Plan: 4 to add, 1 to change, 0 to destroy.") in the pull request, a second engineer reviews it, and the state lives in an encrypted, versioned S3 bucket with locking, so two engineers can never apply at the same moment.
Practice (0/3 done)
Write symbol(action) that returns the symbol Terraform shows in a plan: "create" gives "+", "update" gives "~", "delete" gives "-", "replace" gives "-/+". Any other action gives "" (empty string).
Write plan_summary(actions), where actions is a list such as ["create", "update", "replace", "no-op"]. Return the summary line that Terraform prints: "Plan: X to add, Y to change, Z to destroy." A "create" adds 1, an "update" changes 1, a "delete" destroys 1, and a "replace" counts as 1 add AND 1 destroy. "no-op" counts for nothing.
Two engineers run terraform apply at the same time on the same configuration, with state on their own laptops. What is the main risk?
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- IaC in a team: reviews, pipelines and policyInfrastructure as code · same module
- Terraform basics: HCL, providers, resources and the workflowDevOps: Git, Docker, Kubernetes and CI/CD · Infrastructure as code with Terraform
- Terraform state, backends and driftDevOps: Git, Docker, Kubernetes and CI/CD · Infrastructure as code with Terraform
- Modules, environments and Terraform in CIDevOps: Git, Docker, Kubernetes and CI/CD · Infrastructure as code with Terraform
- Why infrastructure as codeDevOps: Git, Docker, Kubernetes and CI/CD · Infrastructure as code with Terraform
- Go in cloud and DevOps: Kubernetes, Terraform and PrometheusGo Programming (backend and cloud) · CLI tools and Go in cloud and DevOps
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does terraform init do?
Answer: Downloads the providers and sets up the backend
init prepares the working folder: it downloads providers and modules and configures the backend.
In a plan, a resource is marked -/+. What will happen?
Answer: It will be destroyed and a new one created
-/+ means replace: Terraform destroys the old object and creates a new one. Check this carefully for databases.
Why should terraform.tfstate not be committed to Git?
Answer: It can contain secrets in plain text and it must be shared with locking, which Git cannot do
State can hold passwords and keys, and it needs locking to stop two applies at once. Use an encrypted remote backend.
What is the difference between a resource block and a data block?
Answer: A resource block is created and managed by Terraform; a data block only reads something that already exists
Data sources read existing information, like the account ID, without managing it.
Which command checks the syntax and references of your .tf files without contacting the cloud to plan changes?
Answer: terraform validate
validate checks the configuration itself. plan is the step that compares it with real infrastructure.
How do you refer to the id of resource "aws_s3_bucket" "logs"?Related
Answer: aws_s3_bucket.logs.id
A resource address is TYPE.NAME, and you add the attribute after another dot.
From Terraform basics: HCL, providers, resources and the workflow
What does terraform state rm aws_s3_bucket.legacy do to the real bucket?Related
Answer: Nothing: Terraform just stops tracking it
state rm only removes the record. The real resource keeps existing.
What should happen to .terraform.lock.hcl?Related
Answer: Commit it to Git
The lock file pins exact provider versions and checksums for everyone, so it belongs in Git.
Your company has 200 resources that were created in the console. What is the usual way to bring them under Terraform?Related
Answer: Import them (import blocks or terraform import), then plan until it shows no changes
Importing adds existing resources to the state; a clean plan proves the code matches them.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Infrastructure as code” before its exam: IaC in a team: reviews, pipelines and policy.
- Module 9 exam: Infrastructure as code · 12 questions · 20 min, without help. 4 topics of this module still to finish first.
- Then the next module, High availability, scaling and disaster recovery. 50 topics left in the course before the final exam.