ITCertFoundryTechnical training system
AUTOMATION

Terraform on AWS: Provider Setup and First Resource

Configure the AWS provider, authenticate safely, and apply your first Terraform-managed resource.

The provider block

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

Authenticating to AWS

Never hardcode an AWS access key and secret directly in a .tf file — that's the single most common way Terraform credentials end up leaked in a public repo.

The provider automatically checks, in order: environment variables (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY), the shared credentials file (~/.aws/credentials, typically set up via aws configure), and IAM roles when running on AWS infrastructure itself (EC2, CodeBuild, etc.).

For local development, running aws configure once and letting Terraform read the shared credentials file is the simplest safe option.

A first real resource

resource "aws_s3_bucket" "example" {
  bucket = "itcertfoundry-example-bucket-12345"
}

The actual workflow

terraform init
terraform plan
terraform apply
terraform destroy

What each command really does

CommandWhat it doesterraform initDownloads the AWS provider plugin and sets up the local working directoryterraform planShows exactly what would be created, changed, or destroyed — changes nothing yetterraform applyExecutes the plan and actually creates the resources in AWSterraform destroyTears down everything Terraform created, based on its state file

Guardrails worth having from day one

Always run terraform plan and actually read the output before apply — treat it as your last checkpoint to catch a mistake before it becomes a real AWS bill or a real outage.

Store state remotely (an S3 backend with DynamoDB locking is the common AWS-native pattern) the moment more than one person touches the same infrastructure — a local state file doesn't survive a team.

Real scenarios you'll actually face

"terraform apply fails with 'resource already exists' for something I've never touched with Terraform before." This happens when the actual AWS resource (an S3 bucket, an EC2 instance, whatever) already exists — created manually in the console, or by an older script — and Terraform has no record of it in state, so it tries to create it fresh and AWS correctly refuses. `terraform import <resource_address> <real_resource_id>` brings the existing resource under Terraform's management without recreating it — the fix is teaching Terraform about what's already there, not fighting AWS into letting you create a duplicate.

"terraform plan shows it wants to destroy and recreate a resource I only changed slightly." Some resource arguments are immutable after creation (you can't change them in place — AWS's API itself requires a delete-and-recreate for that specific field, not a Terraform limitation), and Terraform's plan will show a destroy followed by a create as ONE combined action for that resource. This is exactly why reading the plan output before every apply matters — a one-line config change you thought was harmless can be the line that takes a resource offline, and plan is the only checkpoint that shows you before it actually happens.

"Two of us ran apply around the same time and now state looks wrong." Without a remote backend with locking (S3 + DynamoDB is the standard AWS pattern), two people running Terraform against the same local state file at the same time can both write to it and corrupt each other's changes — Terraform has no way to know a conflicting apply happened elsewhere. This is precisely the failure mode remote state with locking exists to prevent: the second apply simply waits for the lock instead of racing the first one.

OFFLINE OPTION

Want a printable copy?

This complete guide is also available as a professionally formatted PDF.

Download PDF ↓