This comprehensive guide serves as a practical reference for Terraform, covering everything from core architecture and essential commands to syntax patterns and industry best practices. Whether you're new to Infrastructure as Code (IaC) or an experienced practitioner, you'll find valuable snippets, real-world examples, and curated learning resources to enhance your Terraform skills.
While not intended as a complete tutorial, this guide complements the official Terraform Documentation by providing a focused overview of key concepts and practical implementations. Each section includes references to detailed resources for deeper learning.
What is Terraform?
For short, Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. So you can develop all your infrastructure as code (IaC) and manage it through Terraform (without the need to manually provision and configure resources). It's amazing!
I recommend you to access the Terraform Roadmap to see the steps to become a Terraform expert.
Terraform architecture
Terraform architecture consists in a set of components that work together to manage infrastructure. This components are:
- CLI: The command line interface for Terraform. It is used to interact with the Terraform runtime.
- Configuration files: The configuration is the code that describes the infrastructure that you want to manage. It is written in Terraform Configuration Language (HCL). Which is a declarative language that describes the infrastructure in a human readable format. So you can focus on the what and not the how.
- State: The state is an important component of Terraform. It is a file that stores the details of the managed infrastructure. The state is used to track the details of the infrastructure and to ensure that the desired state is achieved.
- Runtime: The runtime is responsible for executing the Terraform code and managing the state of the infrastructure.
-
Plugins: Plugins are the way Terraform interacts with the infrastructure. They are responsible for executing the Terraform code and managing the state of the infrastructure.
- Providers: Providers are plugins that allow Terraform to manage infrastructure. For example, the AWS provider allows Terraform to manage AWS infrastructure.
- Provisioners: Provisioners are plugins used to execute scripts or commands on the remote resource after it is created. For example, the AWS provider has a provisioner that allows you to execute a script on the remote resource after it is created.
- Modules: Modules are not a component, but a way to package and reuse infrastructure code. They are a fundamental concept in Terraform that allow you to create reusable, modular, and maintainable infrastructure. Some providers have their own modules in the Terraform Registry. For example:
Basic Commands
Now let's see some basic commands to get you started with Terraform. Also you can find a awesome cheat sheet for Terraform commands and syntax here from Nic Wortel.
# The first step is to initialize the Terraform working directory where the state will be stored, the plugins will be downloaded and the backend will be configured through the terraform configuration files `*.tf`.
terraform init
# Preview changes
terraform plan -out=tfplan
# Apply changes
terraform apply "tfplan"
# Taint a resource, this is useful when you need to force a replacement of the resource
terraform taint aws_instance.example && terraform apply "tfplan"
# Destroy infrastructure
terraform destroy
# Format code
terraform fmt
# Validate configuration
terraform validate
# Show current state
terraform show
# Import existing infrastructure
terraform import aws_instance.example i-1234567890abcdef0
Basic Syntax
As mentioned before, Terraform Configuration Language (HCL) is a declarative language that describes the infrastructure in a human readable format. So you can focus on the what and not the how. As programmers we are used to write imperative code, where we tell the computer what to do step by step. But in Terraform we write declarative code, where we tell the computer what we want and the computer figures out how to do it. So let's see some basic syntax for defining a Terraform configuration file.
# First we need to configure the provider
provider "aws" {
region = "us-west-2"
profile = "default"
}
# Then we need to define the resources we want to create
resource "aws_instance" "example" {
ami = "ami-123456"
instance_type = "t2.micro"
tags = {
Name = "example-instance"
}
}
# Then we can use data sources to get information about the infrastructure from the provider.
# In this example we are fetching the latest Ubuntu AMI from the AWS provider.
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
}
# Variables
variable "environment" {
type = string
default = "dev"
description = "Environment name"
}
# Outputs
output "instance_ip" {
value = aws_instance.example.public_ip
}
# Local values, these are useful when you need to define a value that is used in multiple resources.
locals {
common_tags = {
Environment = var.environment
Project = "MyProject"
}
}
✔️ To see more about Terraform configuration language, access the Write Terraform Configuration tutorials in the Terraform documentation. This is a great resource to learn through practical examples and use cases.
Variables and References
- You can specify descriptions, default values, types and constraints, validation rules, and sensitive values.
- Also you can use variables to reference other variables, locals, and data sources.
- Variables can be passed in at runtime, from a file, from environment variables, or from a remote secrets management service.
- Variables types can be
string
,number
,bool
,list
(ortuple
),set
,map
(orobject
), one special type that has no type:null
. - see more at Types and Values
# Variable types
variable "ports" {
type = list(number)
default = [80, 443]
}
variable "settings" {
type = map(string)
default = {
"key1" = "value1"
"key2" = "value2"
}
}
# Variable references
${var.environment}
# Local references, these are useful when you need to reference a value that is defined in a local block.
${local.common_tags}
Ways to populate variables
- Hardcoded values
-
From a file,
terraform.tfvars
or*.auto.tfvars
or*.tfvars.json
- From command line,
terraform plan -var="environment=prod"
- From command line,
terraform plan -var-file="terraform.tfvars"
-
tfvars
example:
environment = "prod" region = "us-west-2" zone = "us-west-2a" instance_type = "t2.micro"
- From command line,
From environment variables,
TF_VAR_environment
From a remote secrets management service like AWS Secrets Manager, Google Secrets Manager, or HashiCorp Vault
Common Functions
# Variable types
variable "ports" {
type = list(number)
default = [80, 443]
}
variable "settings" {
type = map(string)
default = {
"key1" = "value1"
"key2" = "value2"
}
}
# Variable references
${var.environment}
${local.common_tags}
Conditional Expressions
# Ternary operator
condition ? true_val : false_val
# Dynamic blocks
dynamic "setting" {
for_each = var.settings
content {
name = setting.key
value = setting.value
}
}
Count and For Each
# Count example
resource "aws_instance" "server" {
count = 3
ami = "ami-123456"
tags = {
Name = "server-${count.index}"
}
}
# For each example
resource "aws_iam_user" "users" {
for_each = toset(["user1", "user2", "user3"])
name = each.key
}
State Management
# State commands
terraform state list
terraform state show aws_instance.example
terraform state mv aws_instance.example aws_instance.new
terraform state rm aws_instance.example
terraform state pull
terraform state push
Workspaces
Workspaces are used to manage multiple environments (dev, prod, staging, etc.) from the same configuration.
See more about use cases for workspaces.
# Workspace commands
terraform workspace new dev
terraform workspace select prod
terraform workspace list
terraform workspace delete dev
Importing Existing Infrastructure
# Terraform Import And Outputs
# import EC2 instance with id i-abcd1234 into the Terraform resource named "new_ec2_instance" of type "aws_instance"
terraform import aws_instance.new_ec2_instance i-abcd1234
# same as above, imports a real-world resource into an instance of Terraform resource
terraform import 'aws_instance.new_ec2_instance[0]' i-abcd1234
# List all outputs as stated in code
terraform output
# List out a specific declared output
terraform output instance_public_ip
# List all outputs in JSON format
terraform output -json
Modules
Terraform modules are a way to package and reuse infrastructure code. They are a fundamental concept in Terraform that allow you to create reusable, modular, and maintainable infrastructure.
In Terraform you can use modules from the Terraform Registry or you can create your own modules. But I highly recommend using modules from the registry as they are tested and maintained by the Terraform team. So you can focus on building your infrastructure without worrying about the underlying details of the module.
- Terraform Modules
- Terraform Module Registry
- Terraform Module Marketplace
- Terraform Module Snippets
- Terraform Module Development
- Terraform Module Structure
Best Practices
- Use consistent naming conventions
- Organize code into modules
- Version control your Terraform code
- Use remote state storage
- Lock state files when working in teams
- Use data sources instead of hardcoding values
- Implement proper variable validation
- Use terragrunt for keeping your code DRY
- Always use version constraints for providers and modules. This ensures that the module is compatible with the provider version.
Remember to always run terraform plan -out=tfplan
before applying changes and maintain proper documentation for your infrastructure code!
Top comments (0)