Terraform

Terraform by HashiCorp is a popular infrastructure-as-code (IaC) tool that you can use to define cloud infrastructure in a concise configuration language called HashiCorp Configuration Language (HCL). Release can apply your Terraform configuration as part of your application's workflow steps.

Terraform is extensible and can plan and execute infrastructure updates for many cloud and on-premises resources. To keep this guide simple, we'll focus on AWS resources only.

Jump to the section you need:

How Terraform works

Terraform's workflow is commonly described in three distinct stages:

  1. Write: You add Terraform configuration to a repository, specifying what your infrastructure should look like.

  2. Plan: You run Terraform's plan script. Terraform compares live infrastructure with the configuration to output a plan to update infrastructure to represent your configuration.

  3. Apply: You run Terraform's apply script to approve and execute Terraform's plan.

Each time you change (write or update) your Terraform configuration, you will need to run the plan and apply scripts again to update existing infrastructure to be in line with your desired configuration.

When working with ephemeral infrastructure, we recommend you add a less-often described fourth stage to your workflow: Destroy. When you no longer need the infrastructure created by Terraform, you can run Terraform's destroy script, which executes a plan that destroys the infrastructure.

Keep in mind that Release skips over the plan stage in the standard Terraform workflow and changes are applied without manual approval. This could start up services that incur a cost, so it is important to review Terraform configuration changes before merging with a branch tracked by Release.

To avoid surprise costs, limit dynamic configuration determined by environment variables as much as possible. For example, rather than reading an environment variable to determine the size of an RDS database in Terraform, add an explicit value for this to your Terraform configuration.

How Release works with Terraform

Release adds override files to partially override Terraform's backend setting and two blocks in your Terraform configuration: the AWS assume_role and region.

The backend override file looks like this:

terraform {
  backend "local" {}
}

The AWS assume_role and region override file is as follows:

provider "aws" {
  assume_role {
    # ARN for the AWS role with elevated permissions
    role_arn     = "arn:aws:iam::111111111111:role/release/release-elevated-perms-role"

    # AWS session name is set to your environment ID
    session_name = "RELEASE_ENV_ID"
  }

  # AWS region for your application's cluster
  region = "us-west-2"
}

Terraform override files are merged with existing configuration instead of completely replacing configuration.

Release does not override any other sections besides AWS assume_role, AWS region, and Terraform backend. This means that you can still, for example, add tagging to your AWS provider.

Authenticate Release to execute Terraform

To create services in your AWS account, Terraform needs to know how to authenticate as a user on your AWS account.

To begin using Terraform for AWS with Release, contact your Release technical account manager to request that they add an AWS role with elevated privileges to your account.

Release's infrastructure runner will assume this privileged role when executing your Terraform configuration.

How to authenticate with non-AWS Terraform providers

You can add authentication details for other providers to your Terraform configuration. We recommend using environment variables to store authentication strings such as usernames and passwords and marking passwords and other auth tokens as secret.

See How to provide values to Terraform for an overview of how to use environment variables in your Terraform configuration.

How to define infrastructure as code using Terraform

By default, during the plan or apply stages, Terraform looks for files with the .tf extension in the current folder.

This simple Terraform configuration example uses the aws provider to add a VPC called example to the user's AWS account in the us-east-1 region:

# Specify which providers to use
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

# Configure the AWS provider (simplified for this example)
provider "aws" {
  region = "us-east-1"
}

# Create a VPC
resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

You could add this file to your repository, as main.tf, then run terraform plan (Terraform will output the plan to update your AWS account), followed by terraform apply (Terraform will update your AWS account).

How to add Terraform runners to your application in Release

When using Terraform with your Release environments, Release can run terraform plan, terraform apply, and terraform destroy on your behalf.

This enables you to start up cloud infrastructure specific to your Release environments when an environment is created, update infrastructure when an environment is patched, or destroy infrastructure when Release tears down an environment.

Modify your Application Template to instruct Release to run Terraform:

Add an infrastructure section to the Application Template

Here's an example infrastructure section, with two Terraform runners:

infrastructure:
- name: dynamodb-table1
  type: terraform
- name: dynamodb-table2
  type: terraform
  directory: "./dynamodb"
  values: ".release/dynamodb2_values.tfvar"

View the infrastructure schema in our schema documentation.

Add a workflow section to the Application Template

Release's infrastructure runner is only executed during one of Release's three predefined workflow stages.

Release workflows consist of three stages:

  1. Setup: When an environment is created or updated.

  2. Patch: When your code (or in this case, Terraform configuration) is changed.

  3. Teardown: When an environment is destroyed.

During setup and patch, Release executes terraform apply -auto-approve.

During teardown, Release executes terraform destroy -auto-approve.

Here is an example of workflow steps that run infrastructure tasks:

workflows:
- name: setup
  parallelize:
  - step: apply_infrastructure
    halt_on_error: true
    tasks:
    - infrastructure.dynamodb-table1
    - infrastructure.dynamodb-table2
- name: patch
  parallelize:
  - step: apply_infrastructure
    halt_on_error: true
    tasks:
    - infrastructure.dynamodb-table1
    - infrastructure.dynamodb-table2
- name: teardown
  parallelize:
  - step: destroy_infrastructure
    halt_on_error: true
    tasks:
    - infrastructure.dynamodb-table1
    - infrastructure.dynamodb-table2
  - step: remove_environment
    tasks:
    - release.remove_environment

How to provide values to Terraform

Terraform modules often take input variables and you can specify input variables for your Terraform configuration.

You can pass variables to Terraform in three ways:

  1. Using a custom values file.

  2. Using environment variables prefixed with TF_VAR_ in your Release environments.

  3. Using Release-specific environment variables.

Let's look at each method in more detail:

Method 1: Using a custom values file

If you declare a values file as part of your infrastructure runner, Release will substitute environment variables with matching names in the values file before running Terraform.

In the following infrastructure runner declaration, the values key points to a file in the repository at .release/dynamodb2_values.tfvar:

infrastructure:
- name: dynamodb-table2
  type: terraform
  directory: "./dynamodb"
  values: ".release/dynamodb2_values.tfvar"

The contents of the values file could look as follows:

table_name = "test-${RELEASE_ENV_ID}-2"

In this example, before running Terraform, Release will replace ${RELEASE_ENV_ID} with the current Release environment ID and run Terraform with the -var-file= option pointing to this values file.

Method 2: Using environment variables prefixed with TF_VAR_ in your Release environments

Terraform can access environment variables that start with TF_VAR_.

Add environment variables with the TF_VAR_ prefix in their names to your Release environments. Then add Terraform variable blocks for these variables to your configuration, omitting TF_VAR_ from the names.

For example, to use an environment variable called TF_VAR_TEST_TAG, add a Terraform variable block to your configuration:

variable "TEST_TAG" {
  type        = string
  description = "Custom tag for testing"
}

You can use this input when configuring resources, for example:

# Create a VPC with test tag
resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
  tags = {
    "testing/test-tag" = "${var.TEST_TAG}"
  }
}

Method 3: Using Release-specific environment variables

For convenience, Release automatically prefixes all environment variables that start with RELEASE_ with TF_VAR_. Access these variables by adding Terraform variable blocks.

This means you can reference all environment variables that start with RELEASE_ by adding them to a values file or by declaring variable blocks for them.

Use AWS SSM parameters to store output values from Terraform

To save values from Terraform for use in your applications, we recommend using AWS SSM parameters.

The example below saves a Redis cluster address to an SSM parameter, prefixed with your Release environment's ID:

# Create an SSM parameter for the Redis address
resource "aws_ssm_parameter" "redis_address" {
  name        = "/${var.RELEASE_ENV_ID}/redis/address"
  description = "Redis address"
  type        = "String"
  value       = aws_elasticache_cluster.redis.cache_nodes[0].address
}

How to use Terraform modules from private GitHub repositories

Release can download Terraform modules from private GitHub repositories if you have added the Release GitHub app and given it access to the repositories.

To learn how to authorize Release to access your repositories, read our documentation about integrating GitHub with Release.

In the Terraform configuration example below, Release will automatically add an access token to the Git configuration and download your private repository:

module "dynamodb" {
  source = "github.com/YOUR-GH-ACCOUNT/private-repository//dynamodb"
}

Step-by-step summary

To summarize, add Terraform to your application on Release by following these steps:

  1. Add your Terraform configuration to your repository.

  2. Add an infrastructure section with a terraform runner to your Application Template.

  3. Create workflow steps in the application template to execute your infrastructure runners.

  4. Map any environment variables you would like to use with Terraform in your Release application's default environment variables file.

  5. Deploy a new environment to test your Terraform configuration.

Example application with Terraform

To see Terraform in action, work through our Terraform, Flask, and Redis example.

Last updated