DEV Community

丁久
丁久

Posted on • Originally published at dingjiu1989-hue.github.io

Terraform Infrastructure as Code

This article was originally published on AI Study Room. For the full version with working code examples and related articles, visit the original post.

Terraform Infrastructure as Code

Terraform Infrastructure as Code

Terraform has become the standard tool for Infrastructure as Code (IaC). It allows you to define, provision, and manage cloud resources across providers using declarative configuration. This guide covers practical Terraform patterns for production use.

Core Concepts

Terraform uses a declarative approach -- you describe the desired state, and Terraform figures out how to reach it:

main.tf

terraform {

required_version = ">= 1.8"

required_providers {

aws = {

source = "hashicorp/aws"

version = "~> 5.0"

}

}

backend "s3" {

bucket = "myapp-terraform-state"

key = "production/terraform.tfstate"

region = "us-east-1"

}

}

provider "aws" {

region = var.aws_region

}

resource "aws_s3_bucket" "app_data" {

bucket = "myapp-production-data"

tags = {

Name = "Application Data"

Environment = "production"

}

}

Key components: providers connect to cloud APIs, resources define infrastructure components, and the backend stores state.

State Management

State is the most critical part of Terraform. It maps configuration to real-world resources.

Remote State Backend

Always use remote state storage with locking:

backend configuration during init: terraform init -backend-config=backend.hcl

bucket = "company-terraform-state"

key = "env:/${environment}/networking/terraform.tfstate"

region = "us-east-1"

dynamodb_table = "terraform-state-lock"

encrypt = true

DynamoDB provides state locking to prevent concurrent modifications. S3 versioning provides state history for rollback.

State Access for Other Configurations

Share outputs across configurations:

data "terraform_remote_state" "vpc" {

backend = "s3"

config = {

bucket = "company-terraform-state"

key = "env:/production/vpc/terraform.tfstate"

region = "us-east-1"

}

}

resource "aws_instance" "app" {

subnet_id = data.terraform_remote_state.vpc.outputs.private_subnet_ids[0]

}

Module Design

Modules are reusable Terraform configurations. Design them for composability:

modules/vpc/main.tf

variable "vpc_cidr" {

description = "CIDR block for the VPC"

type = string

validation {

condition = can(cidrhost(var.vpc_cidr, 0))

error_message = "Must be a valid CIDR notation."

}

}

variable "environment" {

description = "Environment name for tagging"

type = string

}

resource "aws_vpc" "this" {

cidr_block = var.vpc_cidr

enable_dns_hostnames = true

enable_dns_support = true

tags = {

Name = "vpc-${var.environment}"

Environment = var.environment

}

}

output "vpc_id" {

value = aws_vpc.this.id

}

output "vpc_cidr" {

value = aws_vpc.this.cidr_block

}

Use input validation to catch errors early. Document all variables and outputs with descriptions.

Workspace and Environment Management

Use workspaces or directory structure for environment isolation:

Directory structure:

terraform/

env/

production/

main.tf

terraform.tfvars

staging/

main.tf

terraform.tfvars

Or use Terraform workspaces:

terraform workspace new staging

terraform workspace new production

terraform workspace select staging

terraform plan -var-file=staging.tfvars

Workspaces are simpler but can become confusing with many environments. Directory-based separation is clearer for complex setups.

Terraform Plan and Apply Workflow

The standard workflow in CI/CD:

Initialize with backend

terraform init -backend-config=backend-$ENV.hcl

Format and validate

terraform fmt -check

terraform validate

Plan

terraform plan -out=tfplan -var-file=$ENV.tfvars

Apply (typically in CI with approval gate)

terraform apply tfplan

Never run terraform apply without a plan file in CI. Always review the plan output before applying.

Managing Secrets

Never hardcode secrets. Use variables with sensitive flag:

variable "db_password" {

description = "Database administrator password"

type = string

sensitive = true

}

For secrets that must be in state, encrypt with a key management service:

resource "aws_db_instance" "main" {

password = var.db_password # Still goes to state, but you can encrypt state

}

Better approach: use AWS Secrets Manager or Vault and reference secrets via data sources:

data "aws_secretsmanager_secret_version" "db_password" {

secret_id = "production/db/password"

}

Testing Terraform Code

terraform plan as a Test

Run terraform plan in CI to detect drift and validate changes without applying:

terraform plan -detailed-exitcode

Exit code 0: no changes

Exit code 1: error

Exit code 2: changes needed

Terratest for Integration Tests

// test/vpc_test.go

func TestVPC(t *testing.T) {

terraformOptions := &terraform.Options;{

TerraformDir: "../modules/vpc",

Vars: map[string]interface{}{

"vpc_cidr": "10.0.0.0/16",

"environment": "test",

},

}

defer terraform.Destroy(t, terraformOptions)

terraform.InitAndApply(t, terraformOptions)

output := terraform.Output(t, terraformOptions, "vpc_id")

assert.NotEmpty(t, output)


Read the full article on AI Study Room for complete code examples, comparison tables, and related resources.

Found this useful? Check out more developer guides and tool comparisons on AI Study Room.

Top comments (0)