Skip to content

Instantly share code, notes, and snippets.

@ppar
Last active April 21, 2023 12:31
Show Gist options
  • Save ppar/c4353e812f64f082dc7de8df7f1d6fdd to your computer and use it in GitHub Desktop.
Save ppar/c4353e812f64f082dc7de8df7f1d6fdd to your computer and use it in GitHub Desktop.
How to use a single Terraform repo with multiple independent environments

Summary

This text demonstrates a pattern for using a single repository of Terraform code to manage multiple, completely independent environments that contain the same infrastructure.

It's based on simply overriding the terraform data and config files on each execution with:

  • $TF_DATA_DIR
  • -backend-config=
  • -var-file=

This is useful e.g. for running matching dev / test / stage / prod tiers in parallel.

Relationship to "Workspaces"

This doesn't use Terraform Workspaces, which are intended for a different use case.

But IMO it addresses a common confusion where people expect Workspaces to do something like this (I did, anyway) -- see the bug report & discussion in hashicorp/terraform#16627

Steps

1) Filesystem layout

Create something like this to separate code and env-specific params and state

src/
  foo.tf               # Your tf code, as usual
  variables.tf         # Variables for per-env parameterization
  .terraform.lock.hcl  # (Autogenerated) (Should be version-controlled)
  
environments/
  dev/
    values.tfvars            # Environment-specific values for variables.tf
    tf/                      # Directory for terraform-managed files
      .terraform/            # (Autogenerated) Terraform data directory
      terraform.tfstate      # (Autogenerated) Terraform state file
      acme.tfbackend         # External backend storage configuration
                             # (mutually exclusive w/ terraform.tfstate)                             
  
  test/                      # As above
  staging/
  prod/

2) Create Terraform state backend configuration

2.1) With local terraform.tfstate files

If you're using local terraform.tfstate files, add this empty default section verbatim in your Terraform codebase (src/*.tf):

terraform {
  backend "local" {
  }
}

2.2) With remote Terraform state

If you're using remote backend(s) for Terraform state (http, consul, s3, etc), add a similar empty setion, mentioning the backend driver:

terraform {
  backend "acme" {
  }
}

Create environments/${ENV}/tf/acme.tfbackend. Populate it with whatever parameters your chosen backend needs, pointing to the stored state for this environment.

acme_username  = "foobar_ltd"
acme_bucket    = "foobar_ltd_dev"

For more info, see https://www.terraform.io/language/settings/backends/configuration

3) Initialize the repo:

Do this separately for every environment you're running. This creates terraform data files in ../environments/YOURENV/tf/ and also records the backend config file's location there.

3.1) With local terraform.tfstate files

ENVDIR=../environments/dev
TF_DATA_DIR=${ENVDIR}/tf/.terraform terraform init -backend-config=path=${ENVDIR}/tf/terraform.tfstate

3.2) With remote state backend

ENVDIR=../environments/dev
TF_DATA_DIR=${ENVDIR}/tf/.terraform terraform init -backend-config=${ENVDIR}/tf/terraform.acme.tfbackend

4) Use Terraform

With initialization done, you can use commands such as below to operate your separate terraform-managed environments.

Plan:

ENVDIR=../environments/dev
TF_DATA_DIR=${ENVDIR}/tf/.terraform terraform plan -var-file=${ENVDIR}/values.tfvars -out=${ENVDIR}/tf/plan.tfplan

Apply:

ENVDIR=../environments/dev
TF_DATA_DIR=${ENVDIR}/tf/.terraform terraform apply ${ENVDIR}/tf/plan.tfplan

Show, output, etc:

ENVDIR=../environments/dev
TF_DATA_DIR=${ENVDIR}/tf/.terraform terraform show|output|state|etc ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment