In this blog post we will look at how you would use tfvars to deploy to multiple environments in Azure. Deploying Infrastructure as Code (IaC) to multiple environments, we want to keep scalability in mind along with the thought of removing duplication when possible. Managing infrastructure configurations across different environments can be challenging, this is where using .tfvars comes in and within this blog post I will show you why you using be using them, especially on a per environment basis.
What are tfvars?
Tfvars, short for “Terraform variables,” are an essential aspect of Terraform configuration, they are referenced in Terraform scripts as <file_name>.tfvars
. While working with Terraform, you’re likely already familiar with using variables {}
. Tfvars files come into play as a way to enhance your deployment process by providing a structured means to manage configuration-specific variables.
Think of a .tfvars file as a location for the environment specific variables you intend to use within your Terraform scripts. These files allow you to separate configuration details from your main Terraform code, promoting flexibility in managing your infrastructure deployments. By referencing .tfvars files within your Terraform scripts, you can easily incorporate environment-specific variables and streamline your deployment workflow to different environments easily.
Recommendations for using .tfvars between environments
1. Structure your .tfvars files & environment specific .tfvars
As below, I do recommend a folder per “environment”, this is where you would have <environment>.tfvars
within an environment folder.
New environments are required? No problem – simply create another folder inside environments/
with the required .tfvars
terraform-repo/
├── environments/
│ ├── development/
│ │ ├── development.tfvars # Development environment .tfvars file
│ ├── staging/
│ │ ├── staging.tfvars # Staging environment .tfvars file
│ ├── production/
│ │ ├── production.tfvars # Production environment .tfvars file
├── rg.tf # Terraform to deploy resource group
├── vnet.tf # Terraform to deploy vnet
├── variables.tf # Terraform variables file
2. Define Variables in your main Terraform configuration
I do recommend to define all variables used in your .tfvars files within your main Terraform files using the variable {}
block. This practice centralises variable definitions, making it easier to understand and maintain your project’s configuration.
3. Remember sensitive data
Using .tfvars, sensitive data should not be stored in these files – keep the same thought as how you would store this sort of data, it would still be in the likes of an Azure Key Vault and referencing the secrets etc from Key Vault within your Terraform
How do I reference .tfvars when running Terraform?
.tfvars can be referenced in Terraform commands using the var-file
flag. Terraform dynamically loads environment-specific configurations during deployment.
terraform plan
terraform plan -var-file=environments/development/development.tfvars
terraform apply
terraform apply -var-file=environments/development/development.tfvars
Example using .tfvars to deploy different environments in Azure
In this example I will show how you can reference deploying to different environments in Azure using .tfvars as the environment variables. In this example, we will deploy:
- Azure resource naming, depending on environment
- Azure Resource Group in specific region, depending on environment
- Azure Virual Network in the Resource Groups region and depending on environment:
- Address space
- subnet range
Repository setup as above:
Terraform Setup
rg.tf
will deploy the Resource Group in specific region
# Azure Provider
provider "azurerm" {
features {}
}
# Azure Resource Group
resource "azurerm_resource_group" "rg" {
name = "tamops-rg-${var.environment}"
location = var.location
}
vnet.tf
will deploy the Virtual Network with specific values, depending on environment
# Azure Virtual Network
resource "azurerm_virtual_network" "vnet" {
name = "tamops-vnet-${var.environment}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
address_space = [var.vnet_address_space]
subnet {
name = var.subnet_name
address_prefix = var.subnet_address_prefix
}
}
variables.tf
with the values being referenced in the <environment>.tfvars
variable "environment" {
description = "Environment (e.g production)"
}
variable "location" {
description = "Azure region"
}
variable "vnet_address_space" {
description = "Address space for the virtual network"
}
variable "subnet_name" {
description = "Name of the subnet"
}
variable "subnet_address_prefix" {
description = "Address prefix for the subnet"
}
.tfvars Configurations per environment
development.tfvars
environment = "development"
location = "uksouth"
vnet_address_space = "192.168.0.0/16"
subnet_name = "subnet1"
subnet_address_prefix = "192.168.1.0/24"
staging.tfvars
environment = "staging"
location = "westus2"
vnet_address_space = "172.168.0.0/16"
subnet_name = "subnet1"
subnet_address_prefix = "172.168.1.0/24"
production.tfvars
environment = "production"
location = "eastus2"
vnet_address_space = "10.0.0.0/16"
subnet_name = "subnet1"
subnet_address_prefix = "10.0.1.0/24"
Time to run the Terraform
In the example, lets review a plan for development and production, notice the different values being referenced from .tfvars?
Development
terraform plan -var-file=environments/development/development.tfvars
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.rg will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "uksouth"
+ name = "tamops-rg-development"
}
# azurerm_virtual_network.vnet will be created
+ resource "azurerm_virtual_network" "vnet" {
+ address_space = [
+ "192.168.0.0/16",
]
+ dns_servers = (known after apply)
+ guid = (known after apply)
+ id = (known after apply)
+ location = "uksouth"
+ name = "tamops-vnet-development"
+ resource_group_name = "tamops-rg-development"
+ subnet = [
+ {
+ address_prefix = "192.168.1.0/24"
+ id = (known after apply)
+ name = "subnet1"
+ security_group = ""
},
]
}
Plan: 2 to add, 0 to change, 0 to destroy.
Production
terraform plan -var-file=environments/production/production.tfvars
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.rg will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "eastus2"
+ name = "tamops-rg-production"
}
# azurerm_virtual_network.vnet will be created
+ resource "azurerm_virtual_network" "vnet" {
+ address_space = [
+ "10.0.0.0/16",
]
+ dns_servers = (known after apply)
+ guid = (known after apply)
+ id = (known after apply)
+ location = "eastus2"
+ name = "tamops-vnet-production"
+ resource_group_name = "tamops-rg-production"
+ subnet = [
+ {
+ address_prefix = "10.0.1.0/24"
+ id = (known after apply)
+ name = "subnet1"
+ security_group = ""
},
]
}
Plan: 2 to add, 0 to change, 0 to destroy.
Wrapping up
With implementing these suggestions on how to use .tfvars to effectively deploy to multiple environments using Terraform – adding new environments to your deployment pipeline becomes a seamless process. Simply creating an additional .tfvars file enables you to scale your infrastructure deployments effortlessly, ensuring that your Terraform workflow remains agile and adaptable to evolving business needs.
I struggled with this idea recently. If the Terraform configuration is in the root folder, how do you do init for each environment? Or do all the environments end up in a single state file?
Hi Jeff,
Here’s the fundamental setup for all your Terraform deployments: You’ll reference the root folder and a specific .tfvars file when running Terraform, especially via platforms like GitHub Actions. It will initialize into a separate stage file for each environment. In larger setups, you might want multiple state files per environment, like one for each component such as vnet, VMs, hub, etc.
Example someone where of an environment / component setup:
https://github.com/thomast1906/90DayOfDevOps-Terraform-At-Scale/blob/main/.github/workflows/deploy.yaml#L16-L18
This references a three stage platform within each environment
https://github.com/thomast1906/90DayOfDevOps-Terraform-At-Scale/tree/main/platform
Happy to go further indepth?
Thanks
Thomas
Took a minute but now remember what I ran into. If you using the same root folder for your Terraform configuration, then Terraform downloads specific versions of the provider in that root folder. If you decide to try a new provider version, how can you do that for one environment but not the others, say try it in development first, then production. That scenario crossed my mind when trying to develop a solution like this but didn’t research it any further.