Home/Infrastructure/Practical IaC with Terraform: From Module Design to State Management
InfrastructureTerraformIaC

Practical IaC with Terraform: From Module Design to State Management

Click-ops infrastructure cannot be reproduced. Defining infrastructure as code with Terraform enables version control, code review, and automation.

Practical IaC with Terraform: From Module Design to State Management
Contents
  1. Why You Need IaC
  2. Project Directory Structure
  3. Module Design
  4. Remote State Management
  5. CI/CD Integration
  6. Practical Tips
  7. Console Changes (Drift) and Avoiding Re-creation During Refactors
  8. References: Official Docs

Why You Need IaC

Infrastructure created by clicking around cannot be reproduced. Defining infrastructure as code with Terraform enables version control, code review, and automation.

Project Directory Structure

CODE
terraform/
├── environments/
│   ├── prod/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   └── staging/
└── modules/
    ├── vpc/
    ├── eks/
    └── rds/

Module Design

HCL
# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = true

  tags = merge(var.common_tags, {
    Name = "${var.project}-${var.environment}-vpc"
  })
}

variable "cidr_block" {
  type    = string
  default = "10.0.0.0/16"

  validation {
    condition     = can(cidrhost(var.cidr_block, 0))
 error_message = " CIDR ."
  }
}

Remote State Management

HCL
terraform {
  backend "s3" {
    bucket         = "my-company-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}
Bash
# State
aws dynamodb create-table \
  --table-name terraform-state-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

CI/CD Integration

YAML
name: Terraform
on:
  pull_request:
    paths: ['terraform/**']

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
      - run: terraform fmt -check -recursive
      - run: terraform validate
      - run: terraform plan -out=tfplan

Practical Tips

Never commit tfstate files to git (.gitignore)

CODE
*.tfstate
*.tfstate.backup
.terraform/

Masking sensitive variables

HCL
variable "db_password" {
  type      = string
  sensitive = true
}

Terraform is not just a tool. It is a cultural shift that changes how your team operates infrastructure.

Console Changes (Drift) and Avoiding Re-creation During Refactors

Even with remote state and locking, plan sometimes proposes unexpected deletes and re-creations because someone changed settings in the console or the module structure changed.

Causes

  • During an incident, security group rules or instance sizes were edited directly in the console (drift)
  • Moving a resource into a module or renaming it changes its address, which Terraform reads as "delete old + create new"
  • A resource created outside Terraform was added to code but never imported into state

Verification

Bash
# drift only (compare state with reality, no infra changes)
terraform plan -refresh-only
# for CI: exit code 2 when there are changes
terraform plan -detailed-exitcode -out=tfplan; echo "exit=$?"
# list only resources slated for deletion
terraform show -json tfplan | jq -r '.resource_changes[] | select(.change.actions | index("delete")) | .address'

Fix

  • If the drift was intentional, put it in code; otherwise apply to return to the coded state.
  • When a refactor changes addresses, add a moved block so only the state address moves, with no deletion.
HCL
moved {
  from = aws_security_group.web
  to   = module.web.aws_security_group.this
}
  • Bring existing resources in with an import block (Terraform 1.5+) and adjust code until plan shows no changes.
  • Note: recent Terraform versions support S3-native locking in the S3 backend via use_lockfile = true; check your version's backend docs on whether DynamoDB locking is still recommended.

Prevention

  • Run a -detailed-exitcode drift check on a daily schedule and alert on exit code 2.

References: Official Docs

The primary source for the behavior, configuration, and errors covered in this post is the official documentation below. Check there for version-specific options and exact behavior.

✦ ✦ ✦
Sources and verification

Sources and checks vary by article. Only articles with a recorded check date, scope and sources below were checked, and only within that scope. For other articles, verify against the current docs for your version before applying anything, and email us about errors. Editorial policy · email us

This article has no recorded human check (date and scope) yet.

Published ·
Topic-related official docs (auto-linked · not per-claim sources)Terraform documentation ↗

Comments

Be the first to comment.