When developing enterprise applications, it's crucial to manage different environments like development, staging, and production. This is considered a best practice in the industry, and it's something that big tech companies rely on to maintain stability and ensure smooth deployments. Terraform Workspaces help you do this by keeping these environments separate without needing to duplicate your configuration files. This approach allows you to test changes safely before pushing them to production.
For example, if you're working on a large-scale application, testing new features in a staging environment before they go live is essential. Terraform Workspaces make this possible by creating isolated copies of your infrastructure. Once you're confident the changes work as expected, you can move them to production with less risk, just like the big tech companies do.
In this blog, I'll show you how to set up and use Terraform Workspaces to manage multiple environments effectively. We’ll use a practical example based on the repository terraform-api-workspaces.
Why Use Workspaces for Enterprise Applications?
Using Workspaces is not just a good idea—it’s an industry-standard practice. It helps you manage different environments without mixing them up, keeps things organized, reduces errors, and ensures you can test thoroughly before making any changes live. This is how major tech companies maintain reliable and scalable infrastructure.
Get Started with Workspaces
Follow the steps below to learn how to manage multiple environments with Terraform Workspaces. By the end of this guide, you'll be able to keep your environments separate and your infrastructure stable, following the same practices used by industry leaders.
Step-by-step guide: Managing Multiple Environments with Terraform Workspaces
Before starting, check out our previous post: Step-by-Step Guide: Deploying a REST API in AWS with Terrafom. It covers the basics you’ll need to follow along.
First step: Fork repository
First, fork the repository rest-api-aws-terraform This repo has the Terraform configurations we'll use to demonstrate how Workspaces work.
Second step: initialize Terraform
Execute the command bellow:
$ make init
That is for initialize Terraform
Third Step: Create the Workspaces
In the directory /terraform
At this point you can see what are the workspace created with this command terraform workspace list
$ terraform workspace list
* default
Create the testing workspace with the command terraform workspace new testing
$ terraform workspace new testing
Created and switched to workspace 'testing'!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run 'terraform plan' Terraform will not see any existing state
for this configuration.
Create the development workspace with the command terraform workspace new development
$ terraform workspace new development
Created and switched to workspace 'development'!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run 'terraform plan' Terraform will not see any existing state
for this configuration.
Create the production workspace with the command terraform workspace new production
$ terraform workspace new production
Created and switched to workspace 'production'!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run 'terraform plan' Terraform will not see any existing state
for this configuration.
When a new workspace is created, it automatically switches to that environment. You can switch to another workspace with
terraform workspace select <workspace_name>
$ terraform workspace select development
Switched to workspace 'development'.
You should have a list like this:
$ terraform workspace list
default
* development
production
testing
Fourth step: Config the modules to work with workspaces
In the file /terraform/variables
config the variables to be use with workspaces
...
# Variable for infrastructure environment
variable 'environment' {
description = 'Environment to deploy resources' # Description of the environment variable
type = map(string) # Data type of the variable
default = {
production = 'infra-prod'
development = 'infra-dev'
testing = 'infra-test'
}
}
Modific the file /terraform/main.tf
to use the new variables
# VPC Module
module 'vpc' {
source = './modules/vpc'
vpc_environment = var.environment[terraform.workspace] # Add this environment for VPC deployment
}
# API Gateway Module
module 'api-gateway' {
source = './modules/api-gateway'
users_lambda_invoke_arn = module.lambda.user_lambda_arn
aws_region = var.aws_region
api_gateway_env = var.environment[terraform.workspace] # Add this environment for API Gateway deployment
}
# Lambda Module
module 'lambda' {
source = './modules/lambda'
subnets_ids = module.vpc.subnets_ids
lambda_vpc_id = module.vpc.lambda_vpc_id
lambda_env = var.environment[terraform.workspace] # Add this environment for Lambda deployment
}
Module api-gateway: In the module api-gateway (/terraform/modules/api-gateway
) in the file variables
Add the new variable api_gateway_env
...
variable 'api_gateway_env' {
description = 'Environment to deploy API Gateway resources'
type = string
}
Change the name of the resources in the main file of the module concatenating the new variable + the resource name
# Create an IAM role for API Gateway to push logs to CloudWatch
resource 'aws_iam_role' 'api_gateway_cloudwatch_role' {
name = '${var.api_gateway_env}-api-gateway-cloudwatch-role' # Name of the IAM role
...
}
...
# Create a CloudWatch log group for API Gateway logs
resource 'aws_cloudwatch_log_group' 'api_logs' {
name = '/aws/api_gateway/${var.api_gateway_env}-example-api' # Name of the log group
}
# Create an API Gateway REST API
resource 'aws_api_gateway_rest_api' 'api' {
name = '${var.api_gateway_env}-example-api' # Name of the API Gateway
...
}
...
# Create a stage for the API Gateway REST API
resource 'aws_api_gateway_stage' 'api_gateway_stage' {
...
stage_name = '${var.api_gateway_env}-stage' # Name of the stage
...
}
# Define the Cognito User Pool Authorizer for the API Gateway
resource 'aws_api_gateway_authorizer' 'cognito_authorizer' {
name = '${var.api_gateway_env}-cognito-authorizer'
...
}
# Define a Cognito User Pool
resource 'aws_cognito_user_pool' 'main' {
name = '${var.api_gateway_env}-user-pool'
...
}
# Define a Cognito User Pool Domain
resource 'aws_cognito_user_pool_domain' 'main' {
domain = '${var.api_gateway_env}-pool-domain' # Replace with your desired domain prefix
...
}
# Define a Cognito User Pool Client
resource 'aws_cognito_user_pool_client' 'main' {
name = '${var.api_gateway_env}-user-pool-client'
...
}
Module vpc: In the module vpc (/terraform/modules/vpc
) in the file variables
Add the new variable vpc_environment
variable 'vpc_environment' {
description = 'Environment to deploy VPC resources'
type = string
}
Change the name of the resources in the main file of the module concatenating the new variable + the resource name
// Create a VPC
resource 'aws_vpc' 'lambda_vpc' {
...
tags = {
Name = '${var.vpc_environment}_lambda_vpc'
Environment = var.vpc_environment
}
}
// Create a public subnet
resource 'aws_subnet' 'public_subnet' {
...
tags = {
Name = '${var.vpc_environment}_public_subnet'
Environment = var.vpc_environment
}
}
// Create the first private subnet
resource 'aws_subnet' 'lambda_subnet_a' {
...
tags = {
Name = '${var.vpc_environment}_lambda_subnet_a'
Environment = var.vpc_environment
}
}
// Create the second private subnet
resource 'aws_subnet' 'lambda_subnet_b' {
...
tags = {
Name = '${var.vpc_environment}_lambda_subnet_b'
Environment = var.vpc_environment
}
}
...
// Create a NAT gateway in the public subnet
resource 'aws_nat_gateway' 'nat_gw' {
...
tags = {
Name = '${var.vpc_environment}_nat_gw'
Environment = var.vpc_environment
}
}
// Create an Internet Gateway for the VPC
resource 'aws_internet_gateway' 'igw' {
...
tags = {
Name = '${var.vpc_environment}_igw'
Environment = var.vpc_environment
}
}
// Define a route table for the public subnet
resource 'aws_route_table' 'public_rt' {
...
tags = {
Name = '${var.vpc_environment}_public_rt'
Environment = var.vpc_environment
}
}
// Define a route table for the private subnets
resource 'aws_route_table' 'private_rt' {
...
tags = {
Name = '${var.vpc_environment}_private_rt'
Environment = var.vpc_environment
}
}
...
// Define a default security group for instances
resource 'aws_security_group' 'primary_default' {
name_prefix = '${var.vpc_environment}-default-'
description = 'Default security group for all instances in ${aws_vpc.lambda_vpc.id}'
...
tags = {
Name = '${var.vpc_environment}_primary_default_sg'
Environment = var.vpc_environment
}
}
Module lambda: In the module lambda (/terraform/modules/lambda
) in the file variables
Add the new variable lambda_env
variable 'lambda_env' {
description = 'Environment to deploy Lambda resources'
type = string
}
Change the name of the resources in the main file of the module concatenating the new variable + the resource name
# Create the Lambda function for users
resource 'aws_lambda_function' 'users' {
function_name = '${var.lambda_env}-usersExampleLambda' # Name of the Lambda function
...
}
...
# Create an IAM role that the Lambda function will assume
resource 'aws_iam_role' 'lambda_exec' {
name = '${var.lambda_env}-lambda_exec_role' # Name of the IAM role
...
}
# Create an IAM policy that allows Lambda to log to CloudWatch Logs
resource 'aws_iam_policy' 'lambda_logging' {
name = '${var.lambda_env}-LambdaLogging' # Name of the IAM polic
...
}
...
# Create an IAM policy that allows Lambda to manage ENIs for VPC access
resource 'aws_iam_policy' 'lambda_vpc_access' {
name = '${var.lambda_env}-LambdaVPCAccess' # Name of the IAM policy
...
}
...
# Create a security group for the Lambda function
resource 'aws_security_group' 'lambda_sg' {
...
name = '${var.lambda_env}-lambda_sg' # Name of the security group
...
}
Deploying infrastructure
With these changes now we can deploy the infrastructure for each workspace, from the root of the project
- run
make plan
for see the resource that going to be created - run
make apply
for create the resources on AWS
Change workspace with the command terraform workspace select <workspace_name>
and do the deploy of AWS on all the workspaces
Check the AWS console to verify the resources created
- Resources created on VPC
- Resources created on api-gateway
- Resources created on lambda
How this works?
Terraform Workspaces utilize environment-specific variables to maintain isolation between different stages of your deployment, such as development, testing, and production. When you switch between workspaces, Terraform automatically uses the corresponding environment variables, ensuring that each workspace operates with its unique set of configurations.
For example, if you are working in the development workspace, Terraform will use environment variables specific to development, such as 'infra-dev'. These variables are defined in the /terraform/variables.tf
file and are used throughout your Terraform configurations to differentiate resources between environments. This ensures that your development infrastructure does not interfere with your production setup.
By leveraging these environment variables, Terraform enables seamless transitions between workspaces, making it easier to manage multiple environments and maintain consistency across your infrastructure.
Conclusion
Managing multiple environments with Terraform workspaces is a powerful practice that allows you to keep your infrastructure consistent and organized across different stages of development, testing, and production. By using workspaces, you can ensure that changes are thoroughly tested before being applied to your production environment, reducing the risk of disruptions and errors.
This guide walked you through the process of setting up Terraform workspaces, configuring variables, and deploying infrastructure in each workspace. By following these steps, you can maintain clean and efficient infrastructure-as-code configurations that adapt to your project's needs.
✨Connect with us today and take your software development to the next level. See our services for more details.
Top comments (0)