Getting Started
Install Terraform, write your first configuration, and run through the init / plan / apply loop
Getting Started
This page takes you from zero to a real (small) resource in the cloud. Pick AWS for the examples; the workflow is identical for any provider.
Installation
# macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# Linux (Debian/Ubuntu)
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# Verify
terraform -versionPrefer a version manager — tfenv (or asdf with the terraform plugin) — so each project can pin its own Terraform version via a .terraform-version file.
Your First Configuration
Create a new directory and a single file:
# main.tf
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "hello" {
bucket = "my-first-terraform-bucket-${random_id.suffix.hex}"
}
resource "random_id" "suffix" {
byte_length = 4
}
output "bucket_name" {
value = aws_s3_bucket.hello.id
}That's a complete Terraform project. Four blocks:
terraform {}— version constraints for Terraform itself and the providers it loads.provider "aws" {}— configures how to talk to AWS.resource "aws_s3_bucket" "hello" {}— declares a bucket;hellois the local name (used to reference it elsewhere in HCL).output "bucket_name" {}— exposes a value after apply (handy for piping into other tools).
The Core Workflow
# 1. Initialize — downloads providers and modules into .terraform/
terraform init
# 2. Format and validate
terraform fmt -recursive
terraform validate
# 3. Plan — compute the diff between desired state (HCL) and real state
terraform plan -out=tfplan
# 4. Apply the saved plan
terraform apply tfplan
# 5. Tear it all down when done
terraform destroyWhat init does
- Reads the
required_providersblock, downloads the plugins into.terraform/providers/. - Discovers any
moduleblocks and fetches their sources. - Configures the backend (where the state file lives — local file by default).
Run init again whenever you add a provider, add a module, or change the backend.
What plan shows
Terraform will perform the following actions:
# aws_s3_bucket.hello will be created
+ resource "aws_s3_bucket" "hello" {
+ bucket = (known after apply)
+ force_destroy = false
+ id = (known after apply)
...
}
Plan: 2 to add, 0 to change, 0 to destroy.Symbols to read at a glance:
| Symbol | Meaning |
|---|---|
+ | Resource will be created |
- | Resource will be destroyed |
~ | Resource will be updated in place |
-/+ | Resource will be destroyed and recreated (often because of an immutable field) |
The -/+ symbol is the one to slow down on. It means downtime or data loss. Read the reason ("forces replacement") before applying.
Authentication
Terraform uses your provider's normal credential resolution. For AWS, any of these work:
# Environment variables
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=us-east-1
# Or a named profile from ~/.aws/credentials
export AWS_PROFILE=myproject
# Or an IAM role (preferred in CI)
# - GitHub Actions: OIDC + aws-actions/configure-aws-credentials
# - EC2 / ECS: instance/task IAM role auto-detectedNever hardcode credentials in .tf files — they'd end up in version control and in plain text in the state file.
What's Next
You've apply'd a single resource. Next pages add the building blocks you'll use every day: how providers, variables, outputs, and data sources fit together → Core Concepts.