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
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.
Want a printable copy?
This complete guide is also available as a professionally formatted PDF.
Download PDF ↓