Steven's Knowledge

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 -version

Prefer 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; hello is 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 destroy

What init does

  • Reads the required_providers block, downloads the plugins into .terraform/providers/.
  • Discovers any module blocks 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:

SymbolMeaning
+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-detected

Never 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.

On this page