Skip to content

Terraform_best_practices

This chapter covers best practices for structuring, organizing, and managing Terraform projects.

┌─────────────────────────────────────────────────────────────────────────────┐
│ Terraform Project Structure │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ terraform-project/ │ │
│ │ │ │
│ │ ├── environments/ # Environment-specific configs │ │
│ │ │ ├── dev/ │ │
│ │ │ │ ├── main.tf │ │
│ │ │ │ ├── variables.tf │ │
│ │ │ │ ├── outputs.tf │ │
│ │ │ │ └── terraform.tfvars │ │
│ │ │ ├── staging/ │ │
│ │ │ └── prod/ │ │
│ │ │ │ │
│ │ ├── modules/ # Reusable modules │ │
│ │ │ ├── vpc/ │ │
│ │ │ ├── compute/ │ │
│ │ │ └── database/ │ │
│ │ │ │ │
│ │ ├── .gitignore │ │
│ │ ├── .terraform.lock.hcl │ │
│ │ └── README.md │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
terraform/
├── modules/
│ ├── app/
│ ├── database/
│ └── networking/
├── services/
│ ├── frontend/
│ │ ├── main.tf
│ │ └── variables.tf
│ ├── backend/
│ │ ├── main.tf
│ │ └── variables.tf
│ └── data/
│ ├── main.tf
│ └── variables.tf
└── shared/
└── modules/
# main.tf - Resource definitions
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = local.common_tags
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = local.common_tags
}
variables.tf
variable "environment" {
description = "Environment name"
type = string
}
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
}
variable "availability_zones" {
description = "List of AZs"
type = list(string)
}
outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
locals.tf
locals {
common_tags = {
Environment = var.environment
Project = "MyProject"
ManagedBy = "Terraform"
Owner = "Platform Team"
}
}
versions.tf
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
┌─────────────────────────────────────────────────────────────────────────────┐
│ Terraform Security Best Practices │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Secrets Management │
│ ✓ Use environment variables for sensitive values │
│ ✓ Use -var for CLI injection │
│ ✓ Use Terraform Vault provider │
│ ✓ Never commit secrets to version control │
│ │
│ 2. State Security │
│ ✓ Use remote backend with encryption │
│ ✓ Enable state locking │
│ ✓ Restrict access with IAM policies │
│ │
│ 3. Provider Security │
│ ✓ Pin provider versions │
│ ✓ Use official providers │
│ ✓ Review provider permissions │
│ │
│ 4. Resource Security │
│ ✓ Enable encryption on all storage │
│ ✓ Use least-privilege IAM roles │
│ ✓ Enable security features │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# BAD - Don't do this!
variable "db_password" {
default = "mysecretpassword" # Committed to version control!
}
# GOOD - Use environment variable
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}
# Usage: export TF_VAR_db_password="secret"
# Or: terraform apply -var="db_password=secret"
# BETTER - Use Vault
provider "vault" {
address = "https://vault.example.com"
}
data "vault_kv_secret_v2" "db_creds" {
name = "database/creds"
}
resource "aws_db_instance" "main" {
username = data.vault_kv_secret_v2.db_creds.data.username
password = data.vault_kv_secret_v2.db_creds.data.password
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Allow 5.x but not 6.0
}
random = {
source = "hashicorp/random"
version = "~> 3.5"
}
}
}
┌─────────────────────────────────────────────────────────────────────────────┐
│ Naming Conventions │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Resources: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ resource_type descriptive_name │ │
│ │ aws_instance web_server │ │
│ │ aws_s3_bucket app_assets │ │
│ │ aws_security_group api_sg │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Variables: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ snake_case │ │
│ │ instance_type, vpc_cidr, allowed_cidrs │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Outputs: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ descriptive_name (same as resource when possible) │ │
│ │ instance_id, bucket_name, vpc_id │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Modules: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ kebab-case or descriptive │ │
│ │ vpc, web-server, app-container │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# Resources
resource "aws_vpc" "main" { }
resource "aws_subnet" "public" { }
resource "aws_subnet" "private" { }
resource "aws_security_group" "web" { }
resource "aws_instance" "app_server" { }
resource "aws_db_instance" "main" { }
resource "aws_s3_bucket" "assets" { }
# Variables
variable "environment" { }
variable "vpc_cidr_block" { }
variable "instance_type" { }
variable "allowed_cidr_blocks" { }
# Outputs
output "vpc_id" { }
output "public_subnet_ids" { }
output "web_security_group_id" { }
Terminal window
# Format check
terraform fmt -check
# Validate syntax
terraform validate
# Check formatting before commit
git diff --cached | terraform fmt --check -
Terminal window
# Install tfsec
brew install tfsec
# Run security scan
tfsec .
# CI/CD integration
tfsec --format json --out tfsec-results.json .
Terminal window
# Install checkov
pip install checkov
# Run checks
checkov -d .
# With Terraform plan
terraform plan -out tfplan
checkov -f tfplan
backend.tf
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "environments/prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
# Enable versioning
versioning = true
}
}
┌─────────────────────────────────────────────────────────────────────────────┐
│ GitOps Workflow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ GitOps Pipeline │ │
│ │ │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Commit │───▶│ Plan │───▶│ Review │───▶│ Apply │ │ │
│ │ │ Code │ │ │ │ PR/MR │ │ │ │ │
│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │
│ │ │ │
│ │ Steps: │ │
│ │ 1. Developer commits changes │ │
│ │ 2. CI runs terraform plan │ │
│ │ 3. Plan output added to PR/MR │ │
│ │ 4. Code review with plan output │ │
│ │ 5. After merge, CI applies changes │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# backend.tf with Terraform Cloud
terraform {
backend "remote" {
organization = "my-org"
workspaces {
name = "prod-networking"
# or
prefix = "networking-"
}
}
}
sentinel.hcl
import "tfplan/v2" as tfplan
# Example: Prevent creation of expensive instances
main = rule {
all tfplan.resource_changes as _, rc {
rc.type is "aws_instance" implies
rc.change.after.instance_type in [
"t2.micro",
"t2.small",
"t3.micro",
"t3.small"
]
}
}

In this chapter, you learned:

  • Project Structure: Organizing Terraform code for teams
  • Code Organization: Separating concerns (variables, outputs, locals)
  • Security Best Practices: Secrets, state, provider management
  • Naming Conventions: Consistent naming across resources
  • Testing: Format validation, security scanning
  • GitOps Workflow: Automating Terraform with version control
  • Remote Backend: Secure state management