Day 56 of 90 Days of DevOps Challenge: Shifting from Hardcoded to Modular Infrastructure in DevOps

Vaishnavi DVaishnavi D
4 min read

Yesterday, I took my first meaningful step into the world of Infrastructure as Code (IaC) by working with Terraform. I began by writing a basic script to launch an EC2 instance on AWS, which helped me understand how providers and resources are defined using HashiCorp Configuration Language (HCL). I also explored essential Terraform commands such as init, plan, apply, and destroy, which enabled me to get hands-on experience with provisioning infrastructure in an automated and efficient way.

Building on that foundation, today's focus was on improving the structure and reusability of my Terraform scripts. I refactored the code by introducing input and output variables, organizing configurations into separate files for better clarity, and learning how to provision multiple EC2 instances dynamically. These practices are essential for writing clean, scalable, and maintainable infrastructure code suitable for real-world production environments.

Refactoring with Input Variables

In real-world production environments, hardcoding values like AMI IDs, instance types, or key names is a bad practice. Today, I learned how to abstract these values using input variables.

Why Use Input Variables?

  • Makes the Terraform code reusable across different environments

  • Promotes best practices by separating logic from configuration

  • Reduces the chance of error and increases maintainability

Example: input-vars.tf

variable "ami" {
  description = "Amazon machine image ID"
  default     = "ami-0e53db6fd757e38c7"
}

variable "instance_type" {
  description = "Type of EC2 instance"
  default     = "t2.micro"
}

variable "key_name" {
  description = "AWS key pair name"
  default     = "awslab"
}

Example: main.tf

resource "aws_instance" "zerotoroot_ec2_vm" {
  ami             = var.ami
  instance_type   = var.instance_type
  key_name        = var.key_name
  security_groups = ["default"]
  tags = {
    Name = "AIT-Linux-VM"
  }
}

By passing values through variables, the Terraform file becomes dynamic and clean.

Understanding Output Variables

Output variables are equally important. After a Terraform run, you often want to extract meaningful data such as public IPs, instance IDs, subnet IDs, etc.

Why Output Variables Matter:

  • Help in post-provision automation (e.g., pass IP to Ansible)

  • Provide visibility into provisioned resources

  • Essential for integrating with CI/CD pipelines or other tools

Example: output-vars.tf

output "ec2_vm_public_ip" {
  value = aws_instance.ashokit_ec2_vm.public_ip
}

output "ec2_private_ip" {
  value = aws_instance.ashokit_ec2_vm.private_ip
}

output "ec2_subnet_id" {
  value = aws_instance.ashokit_ec2_vm.subnet_id
}

Structuring Terraform Projects

Today, I also learned how to structure Terraform projects into logical components:

  • main.tf – core resource definitions

  • input-vars.tf – all input variables

  • output-vars.tf – all output declarations

This structure improves readability, collaboration, and modularity, especially when projects grow large.

Provisioning Multiple EC2 Instances Dynamically

One of the most exciting parts of the day was learning how to provision multiple EC2 instances using count, locals, and dynamic tagging.

Why Use Dynamic Provisioning?

  • Ideal for environments like Dev, QA, UAT where setup is similar but needs differentiation

  • Helps avoid repeating similar blocks of code

  • Great for scaling infrastructure on demand

Example: locals block and multiple resources

locals {
  instances_count = 3
  instances_tags = [
    { Name = "Dev-Server" },
    { Name = "QA-Server" },
    { Name = "UAT-Server" }
  ]
}

resource "aws_instance" "zerotoroot_ec2_vm" {
  count           = locals.instances_count
  ami             = var.ami
  instance_type   = var.instance_type
  key_name        = var.key_name
  security_groups = ["default"]
  tags            = locals.instances_tags[count.index]
}

Output for All Public IPs:

output "instance_public_ips" {
  value = aws_instance.zerotoroot_ec2_vm[*].public_ip
}

This was a powerful feature. one block of code manages an entire fleet of VMs with unique tags.

Final Thoughts

Day 56 marked a significant advancement in my Terraform learning path. I transitioned from writing basic scripts to developing scalable, well-structured, and production-ready infrastructure code. Gaining the ability to work with variables, capture outputs, and provision multiple resources dynamically has equipped me to design automation workflows that mirror real-world cloud environments.

Looking ahead, I plan to dive into Terraform Modules. This will allow me to package and reuse infrastructure logic efficiently across different environments and teams.

Thank you for being part of this journey. If you're also exploring Terraform or working on Infrastructure as Code, I would love to connect and exchange ideas as we continue growing together in the world of DevOps.

0
Subscribe to my newsletter

Read articles from Vaishnavi D directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vaishnavi D
Vaishnavi D