Terraform: Your First Infrastructure Stack
Pinnacle Provisions is opening a second location. Gerald asked if you can “copy the IT” to the new restaurant. You pulled up your notes from setting up the first location and realized they say things like “click the orange button” and “the password is on a sticky note on the monitor.” Nothing is documented. Nothing is reproducible. If the building burned down, you would be starting from memory.
Infrastructure as Code (IaC) solves this by letting you define your infrastructure in configuration files that can be stored in Git, reviewed in pull requests, and applied repeatably. Terraform, created by HashiCorp, is the most widely used IaC tool in the industry. In this lab, you will write Terraform configurations that provision the same resources you have been creating by hand, and then destroy them with a single command.
Before You Start
Section titled “Before You Start”You need:
- An AWS Academy Learner Lab environment
- An SSH client on your laptop
- A text editor (VS Code recommended for HCL syntax highlighting)
Declarative vs. Imperative
Section titled “Declarative vs. Imperative”Terraform uses a declarative approach: you describe the desired end state (“I want an EC2 instance with these properties”), and Terraform figures out what actions to take to reach that state. This contrasts with imperative tools like shell scripts, where you list the exact steps (“create this, then create that”). Declarative tools are easier to reason about because you state what you want, not how to get there.
Questions
Section titled “Questions”Watch for the answers to these questions as you follow the tutorial.
- How many resources does
terraform plansay it will create? List the resource types (e.g.,aws_instance,aws_security_group). (5 points) - After
terraform apply, write down the public IP and Instance ID from the Terraform outputs. Verify you can SSH in. (5 points) - When you changed the instance type from t3.micro to t3.small, did
terraform planshow “destroy and recreate” or “update in-place”? Why does this distinction matter? (5 points) - Find the
"arn"of your EC2 instance interraform.tfstate. Write down the 12-digit AWS account ID embedded in the ARN (Amazon Resource Name). (3 points) - After
terraform destroy, what doesterraform showdisplay? (2 points) - Get your TA’s initials showing a successful SSH session into your Terraform-provisioned instance. (5 points)
Tutorial
Section titled “Tutorial”Installing Terraform
Section titled “Installing Terraform”-
Install Terraform on your laptop
Terraform runs on your local machine (or a bastion host) and talks to the AWS Application Programming Interface (API) remotely. Install it based on your operating system:
macOS (with Homebrew):
Terminal window brew tap hashicorp/tapbrew install hashicorp/tap/terraformUbuntu/Debian:
Terminal window wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpgecho "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.listsudo apt update && sudo apt install terraformWindows (with Chocolatey):
Terminal window choco install terraformAlternatively, you can install OpenTofu, an open-source fork of Terraform. The commands are nearly identical; just replace
terraformwithtofu. -
Verify the installation
Terminal window terraform --version -
Configure AWS credentials
Terraform needs AWS credentials to create resources. In AWS Academy, download your credentials from the Learner Lab page (click AWS Details, then Show next to AWS CLI). Copy the credentials block and paste it into
~/.aws/credentials:Terminal window mkdir -p ~/.awsvim ~/.aws/credentialsThe file should look like:
[default]aws_access_key_id=ASIA...aws_secret_access_key=...aws_session_token=...
Writing the Terraform Configuration
Section titled “Writing the Terraform Configuration”Terraform configurations are written in HashiCorp Configuration Language (HCL). You will create three files: main.tf (resources), variables.tf (inputs), and outputs.tf (values to display after apply).
-
Create a project directory
Terminal window mkdir ~/terraform-lab && cd ~/terraform-lab -
Write
main.tfTerminal window vim main.tfAdd the following:
terraform {required_providers {aws = {source = "hashicorp/aws"version = "~> 5.0"}}}provider "aws" {region = "us-east-1"}# Use the default VPC instead of creating a new onedata "aws_vpc" "default" {default = true}# Security Group allowing SSH and HTTPresource "aws_security_group" "web" {name = "cs312-tf-sg"description = "Allow SSH and HTTP"vpc_id = data.aws_vpc.default.idingress {description = "SSH"from_port = 22to_port = 22protocol = "tcp"cidr_blocks = ["0.0.0.0/0"]}ingress {description = "HTTP"from_port = 80to_port = 80protocol = "tcp"cidr_blocks = ["0.0.0.0/0"]}egress {from_port = 0to_port = 0protocol = "-1"cidr_blocks = ["0.0.0.0/0"]}tags = {Name = "cs312-tf-sg"}}# IAM role for ECR read accessresource "aws_iam_role" "ec2_ecr" {name = "cs312-tf-ec2-ecr-role"assume_role_policy = jsonencode({Version = "2012-10-17"Statement = [{Action = "sts:AssumeRole"Effect = "Allow"Principal = {Service = "ec2.amazonaws.com"}}]})}resource "aws_iam_role_policy_attachment" "ecr_readonly" {role = aws_iam_role.ec2_ecr.namepolicy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"}resource "aws_iam_instance_profile" "ec2_profile" {name = "cs312-tf-ec2-profile"role = aws_iam_role.ec2_ecr.name}# EC2 instanceresource "aws_instance" "web" {ami = var.ami_idinstance_type = var.instance_typekey_name = var.key_namevpc_security_group_ids = [aws_security_group.web.id]iam_instance_profile = aws_iam_instance_profile.ec2_profile.nametags = {Name = "cs312-tf-instance"}}Let’s walk through this:
- The
terraformblock specifies which providers (plugins) to use. The AWS provider lets Terraform manage AWS resources. - The
datablock reads existing resources (the default VPC) without creating them. - Each
resourceblock declares a resource Terraform should manage. The first argument is the resource type, the second is a local name. - The IAM (Identity and Access Management) role and instance profile give the EC2 instance permission to pull images from ECR without hardcoding credentials.
- The
-
Write
variables.tfVariables make your configuration reusable. Instead of hardcoding values, you parameterize them:
Terminal window vim variables.tfvariable "ami_id" {description = "AMI ID for the EC2 instance (Ubuntu 24.04 in us-east-1)"type = stringdefault = "ami-0a0e5d9c7acc336f1"}variable "instance_type" {description = "EC2 instance type"type = stringdefault = "t3.micro"}variable "key_name" {description = "Name of the SSH key pair (must already exist in AWS)"type = string} -
Write
outputs.tfOutputs display useful information after
terraform apply:Terminal window vim outputs.tfoutput "instance_public_ip" {description = "Public IP address of the EC2 instance"value = aws_instance.web.public_ip}output "instance_id" {description = "Instance ID"value = aws_instance.web.id}
The Terraform Lifecycle: Init, Plan, Apply
Section titled “The Terraform Lifecycle: Init, Plan, Apply”-
Initialize the project
Terminal window terraform initThis downloads the AWS provider plugin and sets up the working directory. You will see a
.terraform/directory appear; this is where Terraform stores its plugins. You only need to runinitonce per project (or when you change providers). -
Preview the changes
Terminal window terraform plan -var="key_name=cs312-key"Replace
cs312-keywith the name of your existing SSH key pair. Terraform will show you exactly what it plans to create, change, or destroy, without actually doing it. This is the “measure twice” step before “cutting once.”Review the output carefully. You should see several resources listed with a
+symbol, meaning they will be created. Record the first 20 lines for your lab questions. -
Apply the configuration
Terminal window terraform apply -var="key_name=cs312-key"Terraform will show the plan again and ask for confirmation. Type
yes. It will then make API calls to AWS to create each resource. This takes 30-60 seconds.After completion, Terraform prints the output values. Record the public IP and instance ID.
-
Verify by SSHing in
Terminal window ssh -i ~/Downloads/cs312-key.pem ubuntu@<public-ip-from-output>Run
hostnameandwhoamito confirm you are on the new instance.
Modifying Infrastructure
Section titled “Modifying Infrastructure”-
Change the instance type
Edit
variables.tfand change the default instance type fromt3.microtot3.small:default = "t3.small" -
Preview the change
Terminal window terraform plan -var="key_name=cs312-key"Terraform will show whether this change requires an “update in-place” (the instance is modified without being destroyed) or “destroy and recreate” (the old instance is terminated and a new one launched). Record which it shows; the answer reveals how AWS handles instance type changes.
Understanding State
Section titled “Understanding State”-
Inspect the state file
After
terraform apply, Terraform writes aterraform.tfstatefile containing the current state of all managed resources. This is how Terraform knows what exists in AWS:Terminal window cat terraform.tfstate | python3 -m json.tool | head -80Find the
"arn"field for your EC2 instance. An ARN (Amazon Resource Name) is a globally unique identifier for any AWS resource. It contains your account ID, the region, and the resource ID.
Destroying Infrastructure
Section titled “Destroying Infrastructure”-
Tear everything down
Terminal window terraform destroy -var="key_name=cs312-key"Terraform will show you what it plans to destroy and ask for confirmation. Type
yes. All resources will be deleted from AWS. -
Verify nothing remains
Terminal window terraform showThis should display an empty state. You can also check the EC2 console to confirm the instance is terminated.
You have now defined infrastructure as code, provisioned it with a single command, previewed changes before applying them, and destroyed everything cleanly. In the next lab, you will combine Terraform (which creates infrastructure) with Ansible (which configures it) and a CI/CD pipeline (which automates image builds).