Skip to main content

Building AWS Infrastructure with Terraform: My First Hands-on IaC Project

A practical walkthrough of provisioning and securing AWS S3 using Terraform.

Updated 9 min readMarkdown
Building AWS Infrastructure with Terraform: My First Hands-on IaC Project

From writing Terraform configurations to provisioning real AWS infrastructure.

Infrastructure management is an important part of Cloud and DevOps engineering. Traditionally, cloud resources can be created manually through a cloud provider's console. But what happens when infrastructure needs to be repeatable, scalable, and automated?

That's where Infrastructure as Code (IaC) comes in.

I recently completed my first hands-on project using Terraform and AWS, where I provisioned an Amazon S3 bucket, configured security settings, uploaded an object, and managed the complete infrastructure lifecycle through code.

In this article, I'll walk through what I built, how I implemented it, the commands I used, and what I learned along the way.


1. What Is Infrastructure as Code?

Infrastructure as Code (IaC) is the practice of managing and provisioning infrastructure through configuration files instead of manually configuring resources.

For example, instead of opening the AWS Console and manually creating an S3 bucket, we can define the bucket configuration in Terraform.

Terraform reads that configuration and communicates with AWS to create the required infrastructure.

Why use IaC?

  • Automation: Reduce repetitive manual work.
  • Consistency: Reuse the same infrastructure configuration.
  • Version control: Track infrastructure changes using Git.
  • Reproducibility: Recreate infrastructure when needed.
  • Visibility: Review planned changes before applying them.

2. Technologies Used

Here are the tools I used for this project:

Technology Purpose
Terraform Infrastructure as Code
AWS Cloud infrastructure provider
Amazon S3 Object storage
AWS CLI AWS authentication and command-line access
PowerShell Running Terraform commands
HCL Terraform configuration language

AWS Region: ap-south-1 (Mumbai)

3. Project Architecture

The workflow was straightforward:

        Local Machine
        (Windows / PowerShell)
                |
                v
         Terraform CLI
                |
                v
          AWS Provider
                |
                v
          AWS Account
                |
                v
          Amazon S3
          /        \
         v          v
    S3 Bucket   Public Access
                Block
         |
         v
     image.png

Terraform uses the AWS provider to communicate with AWS and manage the resources defined in the configuration.

4. Prerequisites

Before starting, make sure you have:

  1. An AWS account.
  2. Terraform installed.
  3. AWS CLI installed.
  4. AWS credentials configured securely.
  5. A local project directory.
  6. An image file named image.png.

Verify your installations:

terraform -version
aws --version

Configure AWS CLI credentials using:

aws configure

Enter your IAM access key, secret key, default region, and output format when prompted.

Security note: Never commit AWS credentials to GitHub or expose them in screenshots. Use IAM credentials with only the permissions required for your project.

5. Create the Project Directory

Create a folder for your Terraform project.

mkdir terraform-s3-project
cd terraform-s3-project

Create the following project structure:

terraform-s3-project/
│
├── main.tf
└── image.png
  • main.tf contains the Terraform configuration.
  • image.png is the file that will be uploaded to S3.

6. Write the Terraform Configuration

Now comes the main part of the project.

Create a file named main.tf.

Add the following configuration:

provider "aws" {
  region = "ap-south-1"
}

resource "aws_s3_bucket" "my_bucket" {
  bucket = "YOUR-UNIQUE-BUCKET-NAME"

  tags = {
    Project = "Create S3 Bucket with Terraform"
  }
}

resource "aws_s3_bucket_public_access_block" "my_bucket_public_access_block" {
  bucket = aws_s3_bucket.my_bucket.id

  block_public_acls       = true
  ignore_public_acls      = true
  block_public_policy     = true
  restrict_public_buckets = true
}

resource "aws_s3_object" "image" {
  bucket = aws_s3_bucket.my_bucket.id
  key    = "image.png"
  source = "image.png"
}

Replace YOUR-UNIQUE-BUCKET-NAME with a globally unique S3 bucket name.

Let's understand the configuration.

A. AWS Provider

provider "aws" {
  region = "ap-south-1"
}

The provider tells Terraform which cloud platform to communicate with.

Here, we're using the AWS provider and deploying resources in the Mumbai region.

B. S3 Bucket Resource

resource "aws_s3_bucket" "my_bucket" {
  bucket = "YOUR-UNIQUE-BUCKET-NAME"

  tags = {
    Project = "Create S3 Bucket with Terraform"
  }
}

This resource instructs Terraform to create an S3 bucket.

  • aws_s3_bucket: Terraform resource type.
  • my_bucket: Local resource name.
  • bucket: Actual globally unique S3 bucket name.
  • tags: Metadata used to organize and identify resources.

C. Public Access Protection

resource "aws_s3_bucket_public_access_block" "my_bucket_public_access_block" {
  bucket = aws_s3_bucket.my_bucket.id

  block_public_acls       = true
  ignore_public_acls      = true
  block_public_policy     = true
  restrict_public_buckets = true
}

Security is an important consideration when working with cloud infrastructure.

This configuration enables all four S3 public access block settings.

It helps prevent accidental public exposure of the bucket through public ACLs or policies.

Notice this reference:

aws_s3_bucket.my_bucket.id

Instead of manually entering the bucket ID, Terraform references the bucket resource created earlier.

This also establishes a dependency between the resources.

D. Upload an Object to S3

resource "aws_s3_object" "image" {
  bucket = aws_s3_bucket.my_bucket.id
  key    = "image.png"
  source = "image.png"
}

This resource uploads a local image file to the S3 bucket.

  • bucket: Destination bucket.
  • key: Object name inside S3.
  • source: Local file path.

Terraform manages this upload as part of the infrastructure configuration.

Defining AWS infrastructure using Terraform HCL.

7. Initialize the Terraform Project

Once the configuration is ready, initialize Terraform.

terraform init

What happens during initialization?

  • Terraform identifies the required providers.
  • Downloads the AWS provider.
  • Initializes the working directory.
  • Prepares the project for further Terraform operations.

This command is generally the first step when setting up a new Terraform project.

Initializing the project and downloading the AWS provider.

8. Format and Validate the Configuration

Before provisioning resources, format and validate the configuration.

Format the code:

terraform fmt

This automatically formats Terraform files into a consistent style.

Validate the configuration:

terraform validate

This checks whether the Terraform configuration is syntactically valid and internally consistent.

A successful validation means the configuration passed Terraform's validation checks. It does not guarantee that AWS permissions or every resource operation will succeed.

9. Preview Changes Using Terraform Plan

This was one of the most important learning points in my project.

Run:

terraform plan

Terraform compares the desired infrastructure configuration with its current state and generates an execution plan.

It shows which resources Terraform intends to create, update, or destroy.

For example:

Plan: 2 to add, 0 to change, 0 to destroy.

The exact result depends on the current configuration and existing Terraform state.

Why is terraform plan important?

It gives engineers an opportunity to review infrastructure changes before applying them.

This is particularly useful in production environments, where unexpected infrastructure changes can cause outages or security issues.

Reviewing the infrastructure changes before deployment.

10. Deploy Infrastructure Using Terraform Apply

After reviewing the execution plan, deploy the resources.

terraform apply

Terraform displays the proposed changes and asks for confirmation.

Type:

yes

Terraform then communicates with AWS and creates the requested resources.

In my project, the initial deployment created:

  • One S3 bucket.
  • One S3 public access block configuration.

After adding the S3 object resource, I ran terraform apply again to upload the image.

The final infrastructure consisted of three Terraform-managed resources.

11. Verify Resources in AWS Console

After deployment, I checked the AWS Management Console.

Navigate to:

AWS Console → S3 → Buckets → Your Bucket

Verify that:

  • The bucket exists.
  • The bucket is in the expected AWS region.
  • Public access protection is enabled.
  • The uploaded image.png object is visible.

This step helped me connect the Terraform configuration with the actual AWS resources created.

Verifying the successfully provisioned S3 bucket in AWS Console.

12. Troubleshooting: AWS Credentials Error

During the project, I encountered an error:

No valid credential sources found

This happened because Terraform could not find valid AWS credentials.

Terraform needs authentication to communicate with AWS.

I resolved the issue by configuring AWS CLI credentials correctly.

I also verified the AWS identity using:

aws sts get-caller-identity

This command helps confirm which AWS identity is being used.

Key learning: Installing Terraform and configuring an AWS provider is not enough. Terraform also needs valid AWS authentication and appropriate IAM permissions.

13. Destroy Infrastructure Using Terraform

After completing the project, I wanted to clean up the resources to avoid unnecessary AWS charges.

I used:

terraform destroy

Terraform identifies the resources it manages and displays a destruction plan.

After reviewing and confirming the operation, it deletes the managed resources.

In my case, Terraform successfully destroyed all three resources.

Important: terraform destroy deletes infrastructure managed by the current Terraform state. Always review the destruction plan before confirming, especially in shared or production environments.

14. Complete Terraform Workflow

Here is the complete workflow I followed:

1. Write Configuration
        |
        v
2. terraform init
        |
        v
3. terraform fmt
        |
        v
4. terraform validate
        |
        v
5. terraform plan
        |
        v
6. terraform apply
        |
        v
7. Verify in AWS Console
        |
        v
8. terraform destroy

Each command serves a different purpose in the Terraform infrastructure lifecycle.

15. What I Learned

This project helped me understand several important Terraform concepts:

  • How Terraform providers connect to cloud platforms.
  • How to define AWS infrastructure using HCL.
  • How Terraform resources are declared.
  • How resource references establish dependencies.
  • How to configure S3 public access protection.
  • How Terraform uploads and manages objects.
  • Why execution plans are important.
  • How Terraform authenticates with AWS.
  • How to manage the infrastructure lifecycle.

Most importantly, I got practical experience moving from writing configuration files to managing actual cloud resources.

16. What's Next?

This project was my starting point with Terraform.

My next learning goals are:

  1. Terraform Variables and Outputs.
  2. Terraform State Management.
  3. Remote Backend using Amazon S3.
  4. Terraform Modules.
  5. Building AWS networking infrastructure using Terraform.
  6. Deploying one of my own applications using Terraform.

The long-term goal is to move towards reusable, automated, and production-oriented cloud infrastructure.


Final Thoughts

Infrastructure as Code is more than just writing configuration files.

It's about making infrastructure repeatable, manageable, and easier to automate.

This project gave me a practical understanding of how Terraform interacts with AWS and how cloud infrastructure can be managed through code.

One project completed. Many more to build.

If you're already working with Terraform in production, I'd love to know:

What's the first best practice you recommend learning after the basics?

Thanks for reading!

Connect with me: https://www.linkedin.com/in/omkanse/

About the author

  • Backend Engineer at Fintech Company. Building secure fintech systems with OAuth2, JWT, AWS, and Kafka. Writing about backend engineering, authentication, and production systems.

No comments yet. Be the first to comment.