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)