Skip Navigation
Show nav
Heroku Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
View categories

Categories

  • Heroku Architecture
    • Dynos (app containers)
    • Stacks (operating system images)
    • Networking & DNS
    • Platform Policies
    • Platform Principles
  • Command Line
  • Deployment
    • Deploying with Git
    • Deploying with Docker
    • Deployment Integrations
  • Continuous Delivery
    • Continuous Integration
  • Language Support
    • Node.js
    • Ruby
      • Working with Bundler
      • Rails Support
    • Python
      • Background Jobs in Python
      • Working with Django
    • Java
      • Working with Maven
      • Java Database Operations
      • Java Advanced Topics
      • Working with Spring Boot
    • PHP
    • Go
      • Go Dependency Management
    • Scala
    • Clojure
  • Databases & Data Management
    • Heroku Postgres
      • Postgres Basics
      • Postgres Getting Started
      • Postgres Performance
      • Postgres Data Transfer & Preservation
      • Postgres Availability
      • Postgres Special Topics
    • Heroku Data For Redis
    • Apache Kafka on Heroku
    • Other Data Stores
  • Monitoring & Metrics
    • Logging
  • App Performance
  • Add-ons
    • All Add-ons
  • Collaboration
  • Security
    • App Security
    • Identities & Authentication
    • Compliance
  • Heroku Enterprise
    • Private Spaces
      • Infrastructure Networking
    • Enterprise Accounts
    • Enterprise Teams
    • Heroku Connect (Salesforce sync)
      • Heroku Connect Administration
      • Heroku Connect Reference
      • Heroku Connect Troubleshooting
    • Single Sign-on (SSO)
  • Patterns & Best Practices
  • Extending Heroku
    • Platform API
    • App Webhooks
    • Heroku Labs
    • Building Add-ons
      • Add-on Development Tasks
      • Add-on APIs
      • Add-on Guidelines & Requirements
    • Building CLI Plugins
    • Developing Buildpacks
    • Dev Center
  • Accounts & Billing
  • Troubleshooting & Support
  • Integrating with Salesforce
  • Deployment
  • Deployment Integrations
  • Using Terraform with Heroku

Using Terraform with Heroku

English — 日本語に切り替える

Last updated November 07, 2022

Table of Contents

  • Set up Terraform
  • Set up the Heroku provider
  • Provisioning an empty app
  • Deploying code to an app
  • Going further
  • Examples
  • Best practices
  • References

As your collection of Heroku apps grows in number and complexity, the ability to automate the deployment of your entire infrastructure (apps, add-ons, domains, Private Spaces, etc.) becomes more and more valuable.

Hashicorp Terraform is a tool that enables you to configure and deploy a wide range of integrated cloud resources (including Heroku resources) with a simple declarative language called HCL.

This article demonstrates how to use Terraform with Heroku. It provides example configurations for setting up common Heroku resources, along with general Terraform best practices.

Set up Terraform

First, download Terraform and install it.

Before using Terraform, consider the backend that stores the configuration’s current state. This state includes identifiers for all existing resources and the relationships between them. Terraform uses this state to understand what needs to be done to complete a particular action (usually create or destroy).

If you’re just getting started with Terraform, the default local backend simply stores the state in a file. Once you start using Terraform for real projects, you’ll want to store your state information using a remote backend, such as Heroku Postgres.

Local backend

The local backend is the easiest way to get up and running with Terraform. If you don’t specify a backend in a Terraform configuration, then the local backend is used.

This backend stores your deployment’s state in a terraform.tfstate file on your local machine. Do not check the terraform.tfstate file into version control. It contains sensitive data such as secret keys.

The local backend is not recommended for long-term use or collaboration. The state is only saved on the individual machine, so it’s not accessible by other team members and not backed-up to avoid accidental loss. To solve this problem, you can instead use a remote backend, such as a Postgres database.

Remote Postgres backend

Set-up your project for long-term use and collaboration with the pg backend along with the Heroku Postgres add-on. The pg backend is included in Terraform version 0.12 and newer. With this remote backend, Terraform can be run on individual contributors’ machines (just like with the local backend) or as a Heroku app. Heroku’s automated database backups and failover ensures the Terraform state is available and can be restored in case of hardware failure.

To use the pg backend, specify it in your main.tf (or other) configuration file:

terraform {
  backend "pg" {
  }
}

Then, create a Heroku app with a Heroku Postgres add-on, and use its DATABASE_URL config variable as the pg backend’s conn_str:

# Pick a unique app name
$ export APP_NAME=my-terraform-backend

# Create the database
$ heroku create $APP_NAME
$ heroku addons:create heroku-postgresql:mini --app $APP_NAME

# On each machine where it's used, initialize Terraform
# with the database credentials
$ export DATABASE_URL=`heroku config:get DATABASE_URL --app $APP_NAME`
$ terraform init -backend-config="conn_str=$DATABASE_URL"

The previous example uses a Mini Heroku Postgres database. Consider setting a Standard-tier or higher plan such as standard-0 or premium-0 to ensure best performance and availability for more critical use-cases.

See Terraform pg backend docs for more configuration options and examples.

Other remote backends

Although they aren’t covered in this article, a variety of other remote backends are available.

Hashicorp also offers a Remote State Management service and premium Terraform Enterprise product.

Set up the Heroku provider

Complete documentation is available in the Heroku provider docs.

Configuration

After you’ve installed Terraform, create your primary configuration file, main.tf, specifying the Heroku provider. If you’re using the remote pg backend, then this file may already contain the backend configuration.

terraform {
  required_providers {
    heroku = {
      source  = "heroku/heroku"
      version = "~> 5.0"
    }
  }
}

Upgrading

You may check for the latest Heroku provider release version and update your configuration if the version is out of date.

Minor and patch version upgrades are backward compatible, following the practice of semantic versioning. To migrate to a new major version, see the Upgrading guide.

Authorization

When Terraform uses the Heroku provider, it makes requests to the Platform API to create apps, add-ons, and other resources. Requests to the Platform API require an authorization token.

Authorization tokens used by Terraform require global scope to perform the various necessary actions with the Heroku API. If you want to isolate Terraform’s capabilities from your existing Heroku account, you can create a new Heroku account specifically for use with Terraform.

Obtaining an authorization token

First, use the Heroku CLI to ensure that you are logged in to the Heroku account that you want to use with Terraform:

$ heroku whoami

If you need to switch identities, log out and then log in like so:

$ heroku logout
$ heroku login

Second, use the Heroku CLI to generate an authorization token. The --description parameter is a human-readable name to indicate the purpose or identity of each authorization:

$ heroku authorizations:create --description terraform-my-app

Set the returned Token value and the Heroku account’s email address as local environment variables for Terraform.

$ export HEROKU_API_KEY=<TOKEN> HEROKU_EMAIL=<EMAIL>

Environment variables must be exported again in each new terminal/shell you use.

You can use the Heroku CLI to look up the authorization token to use it again in the future. List all the authorizations, and then fetch the token for the desired authorization’s ID:

$ heroku authorizations
$ heroku authorizations:info <ID>

Initialization

Now that Terraform has a valid authorization token, you can initialize the provider:

$ terraform init

If using pg backend, also set the database credentials during init. (See Set-up remote Postgres backend):

$ terraform init -backend-config="conn_str=$DATABASE_URL"

After init completes successfully, you don’t need to run it again unless the provider changes (such as rotating credentials) or set up Terraform on a new computer.

Provisioning an empty app

Complete reference of Terraform resources is available in the Heroku provider docs.

Add an app resource

Append a Heroku app resource to your main.tf file:

variable "example_app_name" {
  description = "Name of the Heroku app provisioned as an example"
}

resource "heroku_app" "example" {
  name   = var.example_app_name
  region = "us"
}

This tells Terraform to provision an empty Heroku app with a name that you will provide to the terraform apply command.

Independent of the app’s Heroku name, Terraform’s identifier for this particular resource is heroku_app.example, the resource type and its name. This identifier may be used as a variable in the configuration and for operations such as import or viewing state.

Plan and apply

Use the terraform apply command to apply the configuration you saved to main.tf:

Replace sushi with a unique name for your app.

$ terraform apply -var example_app_name=sushi

You can split the above command into two commands to ensure that the plan can be reviewed and then exactly applied:

$ terraform plan -var example_app_name=sushi -out=current.tfplan
$ terraform apply current.tfplan

Read more about Terraform’s core workflow.

After apply completes successfully, the resources created by Terraform will be present in the Heroku account associated with Terraform’s authorization token.

View Terraform’s current state to see what’s been created:

$ terraform show

When using the local backend, the output of terraform show is based on the contents of the terraform.tfstate file. When using a remote backend, the output is based on the contents of the backend’s state store.

Deploying code to an app

To use Terraform to deploy code to the empty application you provisioned in Provisioning an empty app, update main.tf to match the following:

terraform {
  required_providers {
    heroku = {
      source  = "heroku/heroku"
      version = "~> 5.0"
    }
  }
}

variable "example_app_name" {
  description = "Name of the Heroku app provisioned as an example"
}

resource "heroku_app" "example" {
  name   = var.example_app_name
  region = "us"
}

# Build code & release to the app
resource "heroku_build" "example" {
  app_id     = heroku_app.example.id
  buildpacks = ["https://github.com/mars/create-react-app-buildpack.git"]

  source {
    url     = "https://github.com/mars/cra-example-app/archive/v2.1.1.tar.gz"
    version = "2.1.1"
  }
}

# Launch the app's web process by scaling-up
resource "heroku_formation" "example" {
  app_id     = heroku_app.example.id
  type       = "web"
  quantity   = 1
  size       = "Standard-1x"
  depends_on = [heroku_build.example]
}

output "example_app_url" {
  value = "https://${heroku_app.example.name}.herokuapp.com"
}

Apply the config, again passing your app name as an input variable:

Replace sushi with your app’s name.

$ terraform apply -var example_app_name=sushi

After terraform apply completes successfully, visit your Heroku app’s URL which is available as an output of the configuration.

$ terraform output example_app_url

If you don’t want to keep the sample around, you can use Terraform to clean it all up:

$ terraform destroy -var example_app_name=sushi

Going further

The Examples section below provides full-fledged configurations you can use to provision complex architectures. This section provides simpler fragments to demonstrate more of what you can do with Terraform:

Creating an app and an add-on

This fragment creates an App resource and Add-on resource in a particular Heroku Team and Heroku region:

variable "heroku_team" {
  description = "Name of the Team (must already exist)"
}

resource "heroku_app" "example" {
  name   = "${var.heroku_team}-example"
  region = "us"

  organization {
    name = var.heroku_team
  }
}

resource "heroku_addon" "papertrail_example" {
  app_id = heroku_app.example.id
  plan   = "papertrail:choklad"
}

Note that the app’s name in this example uses the team’s name as a prefix. Consistent prefixing of resource names makes it much easier to track resources provisioned by Terraform.

Scaling an app

This fragment scales uses a Formation resource to scale an existing app. It also executes a local command (a provisioner health check) to wait until the app launches successfully.

resource "heroku_formation" "example" {
  app_id     = heroku_app.example.id
  type       = "web"
  quantity   = 2
  size       = "Standard-1x"
  depends_on = [heroku_app_release.example]

  provisioner "local-exec" {
    command = "./bin/health-check http://${heroku_app.example.name}.herokuapp.com"
  }
}

Creating a Private Space

This fragment creates a Private Space resource, ensuring it is in the same region as AWS resources:

variable "heroku_enterprise_team" {
  description = "Name of the Enterprise Team (must already exist)"
}

variable "heroku_private_space" {
  description = "Name of the Private Space"
}

variable "aws_region" {
  description = "Amazon Web Services region"
  default     = "us-east-1"
}

variable "aws_to_heroku_private_region" {
  default = {
    "eu-west-1"      = "dublin"
    "eu-central-1"   = "frankfurt"
    "us-west-2"      = "oregon"
    "ap-southeast-2" = "sydney"
    "ap-northeast-1" = "tokyo"
    "us-east-1"      = "virginia"
  }
}

resource "heroku_space" "example" {
  name         = var.heroku_private_space
  organization = var.heroku_enterprise_team
  region       = lookup(var.aws_to_heroku_private_region, var.aws_region)
}

Creating a VPN connection to Google Cloud Platform

This configuration fragment sets up a Heroku Private Space VPN connection with a Google Cloud VPC network (see complete Terraform example):

variable "heroku_vpn" {
  description = "Name of the Heroku VPN connection"
}

module "heroku_vpn_gcp" {
  source = "github.com/heroku-examples/terraform-heroku-vpn-gcp"

  // (config details omitted)
}

resource "heroku_space_vpn_connection" "google" {
  name           = var.heroku_vpn
  space          = heroku_space.example.id
  public_ip      = module.heroku_vpn_gcp.google_vpn_ip
  routable_cidrs = ["${module.heroku_vpn_gcp.google_cidr_block}"]
}

Examples

The following examples provide detailed Terraform configurations to set up Heroku resources, as well as resources on Amazon AWS and Google Cloud Platform:

  • Peering a Private Space with an Amazon VPC
  • Creating a VPN connection between a Private Space and Google Cloud Platform

Best practices

Be careful of config drift

Do not use the Heroku Dashboard or CLI to alter Terraform-managed resources. After a Terraform config is applied, modifying resources from outside of Terraform will cause your Terraform state to become out of sync with your config.

Actions such as scaling dynos, setting config vars, and modifying add-ons should all be performed by updating and re-applying your Terraform config. Otherwise, these differences make it impossible to apply or destroy further until the drifting values are either imported (for new resources) or manually updated in the state.

For more details, see Detecting and Managing Drift with Terraform.

Use consistent name prefixes

When viewing your Terraform-managed resources in the Heroku Dashboard or CLI, it can be difficult to understand how the resources relate to one another. By consistently prefixing your resource names across each configuration, this relationship becomes much easier to understand.

For example, use a prefix input variable:

variable "prefix" {
  description = "High-level name of this configuration, used as a resource name prefix"
  type        = "string"
}

resource "heroku_app" "example-1" {
  name   = "${var.prefix}-example-1"
  region = "us"
}

resource "heroku_app" "example-2" {
  name   = "${var.prefix}-example-2"
  region = "us"
}

Use one Heroku team per Terraform config

To improve the experience of managing Terraformed resources, give each Terraform config its own Heroku Team. This way, the Heroku Team contains everything provisioned by Terraform. Set the team name as an input variable, and then use that variable to set the team for each resource in the config.

variable "heroku_team_name" {
  description = "Name of the Heroku Team owning this complete deployment."
  type        = "string"
}

resource "heroku_app" "example" {
  name   = "example"
  region = "us"

  organization = {
    name = var.heroku_team_name
  }
}

Use provisioner health checks

Sometimes a Terraform-created resource isn’t actually “ready” when the provider says it is. A good example of this is a web app that should not be considered ready until it responds to requests with HTTP status 200.

Terraform solves this problem with Provisioners. These are arbitrary commands that Terraform will wait on and by default fail creation of the resource if the command fails.

resource "heroku_formation" "sushi" {
  app_id     = heroku_app.sushi.id
  type       = "web"
  quantity   = 2
  size       = "Standard-1x"
  depends_on = [heroku_app_release.sushi]

  provisioner "local-exec" {
    command = "./bin/health-check https://${heroku_app.sushi.name}.herokuapp.com/"
  }
}

Example bin/health-check script:

#!/bin/bash

# Check the health of the web service every thirty-seconds
# for up to ten minutes, until it responds with HTTP status 200.

fail_count=1
while true
do
  http_status=$(curl --write-out %{http_code} --silent --output /dev/null $1)

  if [ "$http_status" -eq "200" ]; then
    echo "$(date -u) health check succeeded to $1"
    exit 0
  else
    if [ "$fail_count" -eq "21" ]; then
      echo "$(date -u) health check failed (status $http_status) to $1"
      exit 2
    else
      echo "$(date -u) health check ${fail_count}/20 to $1"
      sleep 30
      fail_count=$[$fail_count +1]
    fi
  fi
done

Configuring infrastructure and operational resources

You can use Terraform to configure any combination of infrastructure and operational resources.

Infrastructure resources include:

  • Apps
  • Add-ons
  • Domains
  • Pipelines
  • Private Spaces

When these resources are provisioned, none of your code is running on Heroku yet. Code must still be deployed and launched, either by a developer through the platform, or by Terraform provisioning operational resources.

Operational resources include:

  • Builds
  • Slugs
  • App releases
  • App formations (i.e., dyno scaling)

By provisioning these resources, Terraform can deploy app code directly and operate the complete system. This makes it possible to release multiple apps simultaneously and orchestrate a mutual release rollback if something goes wrong.

Note that when Terraform is used for operational config, the likelihood of config drift increases.

References

  • Terraform documentation
  • Terraform Heroku provider documentation

Keep reading

  • Deployment Integrations

Feedback

Log in to submit feedback.

WAR Deployment WAR Deployment

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Podcasts
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing

Subscribe to our monthly newsletter

Your email address:

  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Heroku Podcasts
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Facebook
  • Instagram
  • Github
  • LinkedIn
  • YouTube
Heroku is acompany

 © Salesforce.com

  • heroku.com
  • Terms of Service
  • Privacy
  • Cookies
  • Cookie Preferences