Using Terraform tfvars for environment-agnostic deployments

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.

GitHub repository with example code

3 comments

  1. 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?

    1. 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

      1. 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.

Leave a Reply