Introduction
Getting Started
- QuickStart
Patterns
- Languages
- Supported Languages
- Python
- Java
- JavaScript
- TypeScript
- Node.js
- React
- Fastify
- Next.js
- Terraform
- C#
- C++
- C
- Go
- Rust
- Swift
- React Native
- Spring Boot
- Kotlin
- Flutter
- Ruby
- PHP
- Scala
- Perl
- R
- Dart
- Elixir
- Erlang
- Haskell
- Lua
- Julia
- Clojure
- Groovy
- Fortran
- COBOL
- Pascal
- Assembly
- Bash
- PowerShell
- SQL
- PL/SQL
- T-SQL
- MATLAB
- Objective-C
- VBA
- ABAP
- Apex
- Apache Camel
- Crystal
- D
- Delphi
- Elm
- F#
- Hack
- Lisp
- OCaml
- Prolog
- Racket
- Scheme
- Solidity
- Verilog
- VHDL
- Zig
- MongoDB
- ClickHouse
- MySQL
- GraphQL
- Redis
- Cassandra
- Elasticsearch
- Security
- Performance
Integrations
- Code Repositories
- Team Messengers
- Ticketing
Enterprise
Terraform is an open-source infrastructure as code software tool created by HashiCorp. It enables users to define and provision data center infrastructure using a declarative configuration language.
Terraform, despite its powerful infrastructure as code capabilities, has several common anti-patterns that can lead to maintainability issues, security risks, and operational problems. Here are the most important anti-patterns to avoid when writing Terraform code.
// Anti-pattern: Hardcoding sensitive values
resource "aws_db_instance" "database" {
allocated_storage = 10
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
name = "mydb"
username = "admin"
password = "supersecretpassword" // Hardcoded password
}
// Better approach: Use variables and sensitive data management
variable "db_password" {
description = "Password for database"
type = string
sensitive = true
}
resource "aws_db_instance" "database" {
allocated_storage = 10
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
name = "mydb"
username = "admin"
password = var.db_password
}
Hardcoding sensitive values like passwords, API keys, and tokens in your Terraform code is a security risk. Use variables marked as sensitive, environment variables, or secret management solutions like HashiCorp Vault.
// Anti-pattern: Repeating similar resource configurations
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main-vpc"
}
}
resource "aws_subnet" "subnet1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
tags = {
Name = "subnet1"
}
}
resource "aws_subnet" "subnet2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
tags = {
Name = "subnet2"
}
}
// Better approach: Use modules
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.14.0"
name = "main-vpc"
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
tags = {
Environment = "dev"
}
}
Not using modules leads to code duplication and maintenance challenges. Use modules to encapsulate and reuse infrastructure patterns.
// Anti-pattern: Using local state
terraform {
# No backend configuration, defaults to local state
}
// Better approach: Use remote state
terraform {
backend "s3" {
bucket = "terraform-state-bucket"
key = "prod/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Storing state locally prevents collaboration and can lead to state file loss. Use remote state backends like AWS S3, Azure Storage, or Terraform Cloud for team environments.
// Anti-pattern: Remote state without locking
terraform {
backend "s3" {
bucket = "terraform-state-bucket"
key = "prod/terraform.tfstate"
region = "us-west-2"
# No locking mechanism specified
}
}
// Better approach: Enable state locking
terraform {
backend "s3" {
bucket = "terraform-state-bucket"
key = "prod/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-locks" # DynamoDB table for locking
}
}
Without state locking, multiple users can modify infrastructure simultaneously, leading to conflicts and corruption. Use state locking mechanisms like DynamoDB for AWS or Azure Blob Storage leases.
// Anti-pattern: Separate directories for environments
// dev/main.tf
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Environment = "dev"
}
}
// prod/main.tf (duplicated code)
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.medium"
tags = {
Environment = "prod"
}
}
// Better approach: Use workspaces with conditional logic
variable "instance_type" {
type = map(string)
default = {
default = "t2.micro"
prod = "t2.medium"
}
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = lookup(var.instance_type, terraform.workspace, var.instance_type["default"])
tags = {
Environment = terraform.workspace
}
}
Using separate directories for environments leads to code duplication. Use Terraform workspaces or separate state files with shared modules for environment separation.
// Anti-pattern: Hardcoding external resource IDs
resource "aws_security_group_rule" "allow_http" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = "sg-12345678" // Hardcoded ID
}
// Better approach: Use data sources
data "aws_security_group" "selected" {
filter {
name = "tag:Name"
values = ["my-security-group"]
}
}
resource "aws_security_group_rule" "allow_http" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = data.aws_security_group.selected.id
}
Hardcoding IDs of resources not managed by Terraform leads to brittle code. Use data sources to reference external resources dynamically.
// Anti-pattern: Duplicating similar resources
resource "aws_iam_user" "user1" {
name = "user1"
}
resource "aws_iam_user" "user2" {
name = "user2"
}
resource "aws_iam_user" "user3" {
name = "user3"
}
// Better approach: Use count
variable "user_names" {
type = list(string)
default = ["user1", "user2", "user3"]
}
resource "aws_iam_user" "users" {
count = length(var.user_names)
name = var.user_names[count.index]
}
// Or even better: Use for_each
variable "users" {
type = map(object({
department = string
role = string
}))
default = {
user1 = { department = "IT", role = "admin" },
user2 = { department = "HR", role = "viewer" },
user3 = { department = "Finance", role = "viewer" }
}
}
resource "aws_iam_user" "users" {
for_each = var.users
name = each.key
tags = {
Department = each.value.department
Role = each.value.role
}
}
Duplicating resource blocks for similar resources leads to maintenance issues. Use count
or for_each
to create multiple instances of a resource dynamically.
// Anti-pattern: No version constraints
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
# No version constraint
}
}
}
// Better approach: Use version constraints
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
Not specifying version constraints can lead to unexpected behavior when provider versions change. Always specify version constraints for Terraform and providers.
// Anti-pattern: Carelessly using targeted applies
# Running: terraform apply -target=aws_instance.web
// Better approach: Use comprehensive applies or modules
# Organize resources into logical modules
module "web_server" {
source = "./modules/web_server"
# parameters
}
module "database" {
source = "./modules/database"
# parameters
}
Frequently using targeted applies can lead to state inconsistencies. Use targeted applies sparingly and prefer organizing resources into logical modules.
// Anti-pattern: Not exposing important resource attributes
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
// Better approach: Use output values
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
output "web_instance_ip" {
value = aws_instance.web.public_ip
description = "The public IP address of the web server"
}
output "web_instance_dns" {
value = aws_instance.web.public_dns
description = "The public DNS address of the web server"
}
Not using output values makes it difficult to retrieve important resource attributes. Use outputs to expose important information for use in other configurations or for users.
// Anti-pattern: Inconsistent formatting
resource "aws_instance" "web" {ami="ami-0c55b159cbfafe1f0"
instance_type="t2.micro"
tags={
Name="web-server"
Environment="prod"}
}
// Better approach: Use terraform fmt
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "web-server"
Environment = "prod"
}
}
Inconsistent formatting makes code harder to read and review. Use terraform fmt
to automatically format your code according to the standard style.
// Anti-pattern: Not validating before applying
# Directly running: terraform apply
// Better approach: Validate first
# Running: terraform validate
# Then: terraform plan
# Finally: terraform apply
Skipping validation can lead to errors during apply. Use terraform validate
to check for syntax errors and other issues before running terraform plan
or terraform apply
.
// Anti-pattern: Vague resource names
resource "aws_instance" "instance" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
resource "aws_security_group" "sg" {
name = "security-group"
}
// Better approach: Descriptive resource names
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
resource "aws_security_group" "web_server_sg" {
name = "web-server-security-group"
}
Vague resource names make it difficult to understand the purpose of resources. Use descriptive names that indicate the resource’s purpose.
// Anti-pattern: No error handling for data sources
data "aws_ami" "latest_amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
// Better approach: Use count to handle potential errors
data "aws_ami" "latest_amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_instance" "web" {
count = length(data.aws_ami.latest_amazon_linux) > 0 ? 1 : 0
ami = data.aws_ami.latest_amazon_linux.id
instance_type = "t2.micro"
}
Not handling potential errors can lead to failed applies. Use conditional logic with count
or for_each
to handle potential errors.