Provisioning AWS with Terraform
The AWS provider turns Terraform into your AWS console. Configure credentials and region, declare VPCs, security groups, EC2 and S3, and reuse it all with modules, tags and remote state.
What is this?
The hashicorp/aws provider lets Terraform manage thousands of AWS services. You configure the provider with a region and credentials, then declare network, compute and storage resources in HCL. Multiple provider blocks target multiple regions or accounts, and remote state stores the results so teams share one source of truth.
Why do you need it?
- Fully declarative AWS infrastructure — no click-ops and no one-off console changes.
- Every change is planned and reversible through the state file.
- Dev, staging and prod reuse one configuration with different variables.
- Combined with the S3 backend, the whole team manages AWS through code.
Mental model
A site plan handed to a foreman
A Terraform AWS configuration
The foreman reads the plan and directs each trade to build a structure — foundation (VPC), framing (subnets), wiring (security groups). Terraform reads your .tf plan and directs each AWS service to build exactly what is drawn.
How it works
- Configure provider "aws" with a region; credentials come from the shared credentials file, env vars, or an IAM role.
- Declare the network layer: VPC, subnets, internet gateway and route tables.
- Declare compute and storage: EC2 instances, security groups with rules, S3 buckets.
- References wire resources together, for example subnet_id = aws_subnet.pub.id.
- Tags label every resource; outputs expose IPs and ARNs; plan reviews before apply.
A small AWS stack built by Terraform.
Key commands
aws sts get-caller-identityVerify your AWS credentials are valid before planning.
terraform providersConfirm the AWS provider version resolved by the config.
terraform planPreview what will be created in AWS.
terraform applyCreate the declared AWS resources.
terraform state show aws_instance.webInspect the recorded attributes of the EC2 instance.
terraform import aws_instance.web i-1234567890abcdef0Adopt an existing AWS resource into state.
terraform plan -destroyPreview what destroy would remove.
Examples
A minimal web server stack
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } }} provider "aws" { region = "us-east-1"} data "aws_ami" "ubuntu" { most_recent = true filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] } owners = ["099720109477"]} resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" tags = { Name = "dev-vpc" }} resource "aws_subnet" "pub" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" tags = { Name = "dev-pub" }} resource "aws_security_group" "web" { vpc_id = aws_vpc.main.id ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }} resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = "t3.micro" subnet_id = aws_subnet.pub.id vpc_security_group_ids = [aws_security_group.web.id] tags = { Name = "dev-web" }} output "web_public_ip" { value = aws_instance.web.public_ip}The data source always looks up the current Ubuntu AMI instead of hardcoding one, the resources reference each other by attribute, and the output exposes the instance IP. This is the shape of a real, reviewable AWS deployment.
In production
- Landing zones: VPCs, IAM roles, logging and guardrails defined as code.
- Ephemeral test environments created per PR and destroyed on schedule.
- Kubernetes and serverless infrastructure (EKS, Lambda) alongside classic EC2.
- Cost control: enforced tagging plus scheduled destroys for non-prod stacks.
Best practices
- Always tag resources (Name, environment, owner, cost-center).
- Prefer IAM roles and assume-role over long-lived access keys.
- Pin the provider version and use data sources for AMIs — never hardcode.
- Keep credentials out of config; use profiles, env vars or a credentials file.
- Use one provider block per region when deploying multi-region stacks.
Common mistakes
- Hardcoding region-specific AMI IDs that drift or don't exist elsewhere.
- Committing AWS access keys to configuration or repository.
- Not tagging resources, making cost attribution impossible.
- Editing security group rules directly in the AWS console instead of the config.
Common issues
Error: configuring Terraform AWS Provider: no valid credential sourcesNo credentials or region found by the provider.
Run aws configure or set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY and AWS_REGION, then retry.
Error: creating EC2 Instance: InsufficientInstanceCapacityThe chosen instance type has no capacity in that Availability Zone.
Change the instance type or AZ, or retry later.
Error: InvalidAMIID.NotFoundThe AMI ID does not exist in the configured region.
Use a data source to look up the AMI dynamically instead of a hardcoded ID.
Debugging tips
- terraform providers verifies the resolved provider version before debugging resources.
- aws sts get-caller-identity isolates credential problems from config problems.
- TF_LOG=DEBUG terraform plan shows the exact AWS API calls and throttling errors.
- Read the provider docs for argument names; terraform validate catches typos fast.
Interview checkpoint
Hands-on lab
- Verify AWS credentials and configure the provider
- Build a VPC, subnet, security group and EC2 instance
- Use a data source for the AMI and read outputs
- Import an existing resource and clean up with destroy
Provision a Web Server on AWS
40 min · intermediate
Quick quiz
Which provider block configuration sets the target region?
Terraform can adopt existing AWS resources with terraform import.
Which command confirms your AWS credentials are usable?
Cheat sheet
aws configureStore credentials locallyaws sts get-caller-identityVerify credentialsprovider "aws" { region = ... }Set regiondata "aws_ami" "ubuntu" {}Look up current AMItags = { Name = "web" }Tag every resourceterraform import aws_instance.web i-123Adopt existing resource