Explore the diverse range of environments available in Terraform by HashiCorp, from local setups to cloud platforms, and learn how to choose the ideal environment for your infrastructure needs.
While Terraform workspaces might seem like an easy way to handle different environments like development, staging, and production, they're not the most effective method. This article presents a superior approach using a combination of directories, modules, variables, and other best practices to manage your infrastructure code for multiple environments in a more organized, scalable, and reliable manner.
Terraform workspaces can be tempting for managing multiple environments (like dev, staging, prod), but they're not the ideal solution. Here's a better approach:
Organize with directories: Create separate directories for each environment (e.g., dev/
, staging/
, prod/
). This keeps environment-specific code cleanly separated.
Leverage modules: Design reusable Terraform modules for common infrastructure components. This promotes consistency and reduces code duplication.
Variables for flexibility: Define variables within each environment's directory to customize deployments. Use .tfvars
files or input variables during Terraform runs.
Conditional logic: When necessary, use Terraform's conditional logic (count
, for_each
, if
statements) within your modules to create environment-specific variations.
Separate state files: Maintain separate state files for each environment. This prevents accidental modifications across environments and ensures independent deployments.
CI/CD pipelines: Integrate Terraform with your CI/CD system. This automates deployments, enforces code reviews, and provides a clear deployment history.
Why this approach is better than workspaces:
Example:
# modules/network/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
}
# environments/dev/main.tf
module "network" {
source = "../../modules/network"
vpc_cidr = "10.0.0.0/16"
}
# environments/prod/main.tf
module "network" {
source = "../../modules/network"
vpc_cidr = "10.1.0.0/16"
}
This way, you have a clear structure, reusable modules, and environment-specific configurations.
The code represents a Terraform project structured to manage multiple environments (dev, staging, prod) separately. It uses a modular approach, defining a reusable "network" module that creates a VPC and subnets. Each environment directory contains its own configuration files (main.tf, variables.tf) to customize the network module with environment-specific values. This structure promotes code reusability, consistency across environments, and isolation of resources, preventing accidental modifications in one environment from affecting others.
โโโ modules
โ โโโ network
โ โโโ variables.tf
โ โโโ main.tf
โโโ environments
โ โโโ dev
โ โ โโโ main.tf
โ โ โโโ variables.tf
โ โโโ staging
โ โ โโโ main.tf
โ โ โโโ variables.tf
โ โโโ prod
โ โโโ main.tf
โ โโโ variables.tf
โโโ variables.tf
modules/network/variables.tf:
variable "vpc_cidr" {
type = string
}
variable "create_subnet_public" {
type = bool
default = false
}
variable "public_subnet_cidr" {
type = string
default = null
}
modules/network/main.tf:
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "vpc-${var.vpc_cidr}"
}
}
resource "aws_subnet" "public" {
count = var.create_subnet_public ? 1 : 0
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidr
availability_zone = "eu-west-1a"
tags = {
Name = "public-subnet-${var.vpc_cidr}"
}
}
environments/dev/main.tf:
module "network" {
source = "../../modules/network"
vpc_cidr = "10.0.0.0/16"
create_subnet_public = true
public_subnet_cidr = "10.0.1.0/24"
}
environments/dev/variables.tf:
# Dev environment specific variables
environments/prod/main.tf:
module "network" {
source = "../../modules/network"
vpc_cidr = "10.1.0.0/16"
}
environments/prod/variables.tf:
# Prod environment specific variables
How to use:
environments/dev
) and run terraform init
.terraform apply
to create or modify infrastructure for that environment.terraform destroy
to delete the resources for that environment.Benefits:
network
module is reused across environments, promoting consistency and reducing code duplication.This is a basic example, and you can expand it further by adding more modules, variables, and conditional logic to suit your specific needs. Remember to integrate this structure with your CI/CD pipeline for automated deployments and a robust workflow.
Remember: This is a general guideline, and the best approach may vary depending on your specific needs and project requirements. Always prioritize clarity, maintainability, and security when designing your Terraform workflow.
While tempting, using Terraform workspaces for managing multiple environments (dev, staging, prod) isn't ideal. This table summarizes a superior approach:
Feature | Description | Benefits |
---|---|---|
Directory Structure | Organize code into separate directories for each environment (e.g., dev/ , staging/ , prod/ ). |
Improved clarity and maintainability. |
Modules | Design reusable Terraform modules for common infrastructure components. | Promotes consistency and reduces code duplication. |
Variables | Utilize variables within each environment's directory to customize deployments. | Provides flexibility for environment-specific configurations. |
Conditional Logic | Employ Terraform's conditional logic (count , for_each , if ) within modules for environment-specific variations. |
Tailors deployments without unnecessary code repetition. |
Separate State Files | Maintain independent state files for each environment. | Prevents accidental cross-environment modifications and ensures isolated deployments. |
CI/CD Integration | Integrate Terraform with your CI/CD pipeline. | Automates deployments, enforces code reviews, and provides a clear deployment history. |
Advantages over Workspaces:
Example:
Reusable network module (modules/network/main.tf
):
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
}
Environment-specific configurations:
environments/dev/main.tf
:
module "network" {
source = "../../modules/network"
vpc_cidr = "10.0.0.0/16"
}
environments/prod/main.tf
:
module "network" {
source = "../../modules/network"
vpc_cidr = "10.1.0.0/16"
}
This approach provides a structured, reusable, and maintainable way to manage multiple environments in Terraform.
In conclusion, while Terraform workspaces might appear as a straightforward solution for managing different environments, a more robust and scalable approach involves leveraging directories, modules, variables, and conditional logic. This method provides a clearer structure, promotes code reusability, and ensures better isolation of environments, ultimately leading to a more maintainable and reliable infrastructure management workflow. By adopting these best practices and integrating them with a robust CI/CD pipeline, organizations can streamline their infrastructure deployments and enhance the overall efficiency of their development process.