4 minute read Published: Author: Colan Schwartz
Terraform , OpenStack , GitLab , Cloud Architecture , Automation , IaaS


Terraform is an essential tool for automating cloud-computing infrastructure and storing it in code (IaC). While there are several ways to navigate between deployment environments (e.g. Dev, Staging & Prod), I’d like to talk about how this can be done with environment variables, and explain why it can’t be done more naturally with Terraform variables.

Background

I originally wrote this as a comment on the feature request Using variables in terraform backend config block, which explains Terraform’s design limitations preventing it from allowing any variables in backend configurations, which is what Terraform uses to determine where to store its state data files (which track the cloud resources it manages).

To illustrate, this is where variables can’t be used, even though the configuration must change when the environment changes:

terraform {
  backend "whatever" {
    [...]
  }
}

Alternatives to using environment variables

Alternatively, it’s possible to have a single backend state configuration that stores data for multiple environments using Workspaces. You can then use Terraform CLI commands to switch between them. However, this solution is not not appropriate for all use cases, as per the documentation:

Workspaces are not appropriate for system decomposition or deployments requiring separate credentials and access controls.

If this isn’t a problem for you, Workspaces is a good option. Otherwise, please keep reading.

Solution

In demonstrating a solution, I’m going to be working with OpenStack, which is the IaaS run by many cloud providers, but the same concept can be applied to other IaaS, such as AWS, Azure, GCP, etc. Here, the backend state data will be stored in OpenStack’s Swift object storage service.

Update (2023-04-11): While the example below is still valid conceptually, Swift can no longer be used as a Terraform backend because support for it has been removed. If you’re still using Swift for this purpose, the official migration documentation is sparse. However, I provide guidance in a more recent article: Moving Terraform State from OpenStack Swift to GitLab.

Typically, you’d download an OpenStack RC file, which contains the credentials needed to access the API. In each of these (e.g. vexxhost-abc-prod-ca.openrc.sh, where the format is provider-project-environment-region), I append the following lines to the end:

[...]

#############################################################################
## Additional set-up not included with IaaS provider's OpenStack RC files ###
#############################################################################
export OS_PROJECT_ENVIRONMENT="abc-prod-ca"
source $(dirname "$0")/setup-terraform-variables.sh

The sourced (included) file then looks like:

#!/usr/bin/env bash
#############################################################################
## Set up Terraform variables.
#############################################################################

# Set up backend state container names.
export OS_PROJECT_STATE="$OS_PROJECT_ENVIRONMENT-terraform-state"
export OS_PROJECT_STATE_ARCHIVE="$OS_PROJECT_STATE-archive"
export TF_CLI_ARGS_init="\
-backend-config='container=$OS_PROJECT_STATE' \
-backend-config='archive_container=$OS_PROJECT_STATE_ARCHIVE'"

# You can set other TF variables in here as well.
echo "Please enter the outgoing e-mail account's password: "
read -sr TF_VAR_smtp_password_unquoted
export TF_VAR_smtp_password="\"$TF_VAR_smtp_password_unquoted\""

The special environment variable here is TF_CLI_ARGS_init. This is what Terraform uses to configure the backend, if it’s set. So by changing that environment variable, you can change the backend configuration. All that’s needed in the code is the following, basically just the backend type, with details being pulled in from the environment.

In main.tf (or wherever), the backend definition:

terraform {
  backend "swift" {
    # Must be read from environment variable `TF_CLI_ARGS_init` because normal
    # variables cannot be used here.
  }
}

Switching between environments

To switch between environments, I simply source another OpenStack RC file; I have one for every environment that I care about. With the above set-up, it also switches the Swift object storage containers used for backend state. You can then run terraform init.

I’ve found that sometimes it’s necessary to delete your .terraform directory before rerunning terraform init though, or it’ll get confused and you’ll get strange error messages. So as standard practise, I run the following command to remove it first:

mv .terraform /tmp

Summary

  1. You cannot use Terraform variables to vary the backend state configuration.
  2. You can, however, use the environment variable TF_CLI_ARGS_init instead.
  3. Run a script to set this environment variable with the configuration matching the deployment environment you’d like to work with.
  4. Simply specify the backend type in the Terraform code, and TF_CLI_ARGS_init along with your script(s) will take care of the rest.

The article Setting Deployment Environments’ Terraform State Backends with Environment Variables first appeared on the Consensus Enterprises blog.

We've disabled blog comments to prevent spam, but if you have questions or comments about this post, get in touch!