What was I trying to solve?
We can build cloud architectures by clicking through a console. Yes, that works, but only once. It leaves you something that is difficult to review, reliably repeat and the sequence only exists in your memory. I have already built a highly available AWS architecture manually, but what if I need to replicate the build multiple times? This brings up a problem.
How do you make an architecture reproducible, reviewable, and safe to change, instead of a one-time sequence of console clicks?
What did I build?
Taking the web architecture I built manually on the AWS console, I reimplemented it using an Infrastructure as Code tool Terraform that enables me to automate the creation, management and updates of the architecture.

This project took on two stages/forms: first one mainly served as my knowledge and understanding of Terraform then the second was the final form which shows improvements in a lot of areas that practices the standards of Terraform usage or in production environments, which will be talked about in another article post.
Being a new user of this tool, Terraform, I took things easy at the start. It was important for me to understand first the basics and fundamentals of Terraform, how it works.
So, these are what the project intentionally does not include yet:
- No variables
- No variables.tf
- No outputs.tf
- No modularization
Everything was hardcoded directly into the resource blocks and every resource lives in one folder instead of being split into reusable modules.
Here are the steps I took
Set up remote state. A small one-time state-bootstrap project created the S3 bucket that holds Terraform’s state.

Put guard rails in first. I created the
.gitignoreand rangit initbefore writing any real resources, so state files and secrets could never be committed by accident.

Build in layers. Each layer got a
planand then anapply:
- Networking: VPC, subnets, gateways, route tables
- Security groups
- Database: Multi-AZ RDS
- Compute: launch template and Auto Scaling Group
- Traffic: target group and load balancer
- Storage and monitoring: S3, CloudWatch, SNS
Test in the console and browser. Terraform stops at
apply, so load balancing, auto-healing, and database failover were verified by hand.Tear it all down with one command.
terraform destroyCommit and push the code, with documentation, to GitHub.
Where Terraform made it easier
I never wrote “create the VPC first.” A reference was enough, and Terraform worked out the order:
Security rules stayed correct as the infrastructure changed. Each security group trusts the previous one by reference, not by IP address:

Secrets never appeared in the code. The database password is generated, stored in Secrets Manager, and passed to RDS by reference. It exists nowhere as plaintext.

Every change was previewable. terraform plan showed exactly what would be added or destroyed before anything was touched. Reading plan output became my safety check.
Teardown was one command. terraform destroy removed everything in the correct order. In the console, I had to remember that order myself.
The setup is documented and repeatable. The whole architecture lives in version-controlled files that anyone can read, review, and rebuild.
The Challenges I encountered
1. Deprecated backend parameter**
While setting up remote state for the project, I configured Terraform’s S3 backend using a DynamoDB table for state locking — the standard approach I’d read about in most tutorials. Running terraform init, though, returned a deprecation warning: dynamodb_table was being phased out in favor of a newer parameter, use_lockfile, which relies on S3’s own native conditional-write locking instead of requiring a separate DynamoDB table.
It wasn’t a breaking error — my original setup still worked — but it was a clear signal that the “standard” approach I’d learned was already on its way out. Since I was still early in the build, I switched to use_lockfile immediately rather than build the rest of the project on a pattern Terraform had already flagged for removal.
2. Overnight cost uncertainty
Partway through the build, I had to stop for the night with a NAT Gateway and a Multi-AZ RDS instance already live — two of the most expensive, non-free-tier resources in the whole architecture. I didn’t want to tear everything down and lose my progress, but I also didn’t want to wake up to a billing surprise.
Rather than guess, I worked out the actual math: NAT Gateway at roughly $0.045/hour, multi-AZ RDS at roughly $0.068/hour, multiplied by a ~10-12 hour overnight window — landing at under $1.50 total. That small bit of arithmetic turned an anxious guess into a confident decision.
Key Takeaways from this project:
Tutorials and guide material can lag behind a tool’s actual current best practices — it’s worth paying attention to deprecation warnings even when they don’t block you, since they’re often a preview of what “wrong” will look like in the future.
Cloud cost awareness isn’t just knowing what’s expensive in the abstract — it’s being able to do quick, back-of-envelope math on your specific running resources to make an informed call, instead of either panicking or ignoring the risk entirely.
Missed opportunity or mistake I realized:
I wasn't aware that I can just import my existing AWS resources to terraform state. If I did that, I didn't need to destroy my architecture then recreate it using Terraform.
Doing this approach in reimplementing my web architecture through Terraform, I learned about how Terraform works and how quite helpful it is to make building AWS projects much quicker and efficiently.
Onto the next article, I will discuss what were the improvements for this project to practice production standards.
Top comments (0)