Infrastructure as Code with Terraform

Part 8 of the AWS Cloud Resume Challenge — in progress.

Status: Terraform for this project is actively in progress. The live site and APIs already run in AWS; they were built with the console, CLI deploy helpers, and GitHub Actions. The next change is to bring that infrastructure under Terraform so future updates are versioned, reviewable, and reproducible.

After completing the frontend and backend portions of my Cloud Resume Challenge using the AWS console, I reached an important realization. While manually configuring infrastructure is useful for learning, it is not how production cloud environments are managed long term.

Modern cloud infrastructure is built using Infrastructure as Code (IaC). Instead of only clicking through the AWS console, engineers define infrastructure in code so it can be version controlled, replicated, and deployed automatically.

This page is the roadmap and working notes for migrating the existing stack into HashiCorp Terraform — without destroying or recreating production. When the .tf modules are ready, they will live in a dedicated IaC repo and manage resources such as DynamoDB, Lambda, API Gateway, and (later) hosting components.

flowchart TD
  today[Today: Console + CLI scripts + GitHub Actions] --> next[Next: Terraform modules in git]
  next --> import[Import or redefine live AWS resources]
  import --> plan["terraform plan until clean"]
  plan --> apply[Manage future changes as code]
						
This migration is planned in two main phases:
  • Importing or mapping existing AWS resources into Terraform
  • Automating ongoing infrastructure changes with Terraform configuration in git

Infrastructure today (before Terraform owns it)

The website is already fully operational. Current management is still a mix of AWS console history, PowerShell/AWS CLI deploy scripts, and GitHub Actions — not checked-in .tf files yet.

The backend architecture included:
  • Amazon S3 - Static website hosting
  • Amazon CloudFront - Global CDN and HTTPS
  • Amazon Route 53 - DNS configuration
  • AWS Lambda - Serverless compute for the visitor counter
  • Amazon DynamoDB - Database storing visitor count
  • Amazon API Gateway - Public API endpoint used by the website
Because the site is already live, deleting and recreating these resources would cause unnecessary downtime. The plan is to use Terraform Import (or carefully authored config aligned to live IDs) so existing resources move under Terraform management safely.

Preparing the Terraform Environment

Before beginning the migration, I configured my local development environment with the tools required to manage infrastructure through Terraform.

Tools used:
  • Visual Studio Code
  • Terraform CLI
  • AWS CLI
The AWS CLI was configured with credentials from an IAM user that has permission to manage the resources used in the project. This allows Terraform to authenticate with AWS and interact with infrastructure programmatically.

Phase 1 - Importing Existing Infrastructure (planned / in progress)

The first phase imports existing AWS infrastructure into Terraform. Instead of recreating resources, Terraform can link configuration files to infrastructure that already exists.

Step 1.1 - Initializing the Terraform Project

I created a Terraform project directory and defined the AWS provider configuration.
terraform {
required_providers {
    aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
    }
  }
}
							

provider "aws" {
region = "us-east-1"
}
This tells Terraform which provider to use and which AWS region the resources exist in.
After creating these files, I initialized the project using:
terraform init
This downloads the necessary Terraform providers and prepares the project for use.

Step 1.2 - Creating Resource Skeletons

Terraform requires a resource block to exist before infrastructure can be imported. To prepare for importing the resources, I created minimal placeholder definitions for the existing infrastructure.

Example DynamoDB definition:
resource "aws_dynamodb_table" "visitor_counter" {
name         = "MyResumeViewCount"
billing_mode = "PAY_PER_REQUEST"
hash_key     = "id"

attribute {
name = "id"
type = "S"
  }
}
These resource definitions act as placeholders so Terraform can map them to the real AWS resources.

Step 1.3 - Importing Existing Resources

With the Terraform resource blocks defined, I used the Terraform CLI to import the existing AWS resources.

Example import commands:
terraform import aws_dynamodb_table.visitor_counter MyResumeViewCount
terraform import aws_lambda_function.visitor_counter MyResumeViewCount
terraform import aws_apigatewayv2_api.visitor_counter_api pzazgc34z6
Each command connects Terraform configuration to an existing AWS resource using its unique identifier. After importing, Terraform now recognizes these resources as part of its managed infrastructure.

Step 1.4 - Verifying Terraform State

After importing the resources, Terraform stores their information in the state file. To verify the resources were successfully imported, I ran:

terraform state list
Example output:
aws_apigatewayv2_api.visitor_counter_api
aws_dynamodb_table.visitor_counter
aws_lambda_function.visitor_counter
This confirmed that Terraform was now aware of the existing infrastructure.

Step 1.5 - Reconciling Terraform Configuration

Once resources are imported, Terraform may detect differences between the configuration files and the real infrastructure. To reconcile these differences, I repeatedly ran:

terraform plan
Terraform compares the configuration files with the live infrastructure and displays any discrepancies. The goal was to update the Terraform configuration until Terraform reported:
No changes. Your infrastructure matches the configuration.
At that point, Terraform was fully aligned with the real AWS environment.

Phase 2 - Automating Lambda Packaging

Once Terraform was managing the infrastructure, I improved the Lambda deployment process. Previously, the Lambda function had been uploaded manually as a ZIP file. Using Terraform, I automated this process using the archive provider.

Example configuration:
data "archive_file" "visitor_counter_zip" {
  type        = "zip"
  source_file = "../lambda/lambda_function.py"
  output_path = "${path.module}/build/visitor_counter.zip"
}
Terraform now automatically packages the Lambda code during deployment.

Protecting the Live Infrastructure

Because the visitor counter Lambda function was already running in production, I added safeguards to prevent Terraform from redeploying the function during the migration.

Example lifecycle rule:
lifecycle {
  ignore_changes = [
    filename,
    source_code_hash
  ]
}
This allowed Terraform to manage the infrastructure safely without disrupting the live website.

Terraform Best Practices Implemented

While migrating the infrastructure, I implemented several Terraform best practices.

Git Ignore Rules

To prevent sensitive information from being committed to GitHub, I added Terraform state files to .gitignore. Example entries:
terraform.tfstate
terraform.tfstate.*
*.tfvars
build/

Target architecture under Terraform

When this migration is complete, the same live backend and hosting components will be managed through Terraform modules in git — not recreated from scratch on day one.

The target set includes:
  • S3 - Static website hosting
  • CloudFront - Global CDN and HTTPS
  • Route 53 - DNS management
  • Lambda - Serverless compute
  • DynamoDB - Visitor counter / contact storage
  • API Gateway - Backend API endpoints

Until then, deploy scripts and GitHub Actions remain the operational path. Terraform is the next change, not a finished claim.

What I’m optimizing for

Moving this project to Terraform is about production habits, not checkbox completion:

  • Reproducible infrastructure — define once, recreate consistently
  • Safe adoption of existing systems — import/align live resources instead of tearing them down
  • State and secrets hygiene — keep tfstate and sensitive values out of git
  • Plan before apply — use terraform plan to avoid accidental production drift

What's Next

CI/CD already ships the website. The immediate Terraform steps are publishing starter .tf modules, importing the visitor and contact APIs, then expanding to edge/DNS resources.

Follow this page for updates as the Terraform repo and state backend come online.