Automate AWS deployments with HCP Terraform and GitHub Actions
Learn how to use GitHub Actions to automate HCP Terraform operations.
Saravanan Gnanaguru is a HashiCorp Ambassador
Using GitHub Actions with HashiCorp Terraform to automate infrastructure as code workflows directly from version control is a popular early path for many developer teams. However, this setup can make it difficult to stop configuration drift as your infrastructure codebase grows.
Rather than running Terraform on the GitHub Actions instance runner, it’s much easier and safer to run configurations remotely via HCP Terraform. This ensures that the creation, modification, and deletion of Terraform resources is handled on a managed cloud platform rather than on the GitHub Actions runner. HCP Terraform has many more systems and safeguards for team Terraform management and drift prevention.
This post shows how to use HCP Terraform to define AWS infrastructure and GitHub Actions to automate infrastructure changes. You’ll learn how to set up a GitHub Actions workflow that interacts with HCP Terraform to automate the deployment of AWS infrastructure, such as Amazon EC2 instances.
» Workflow overview
For this tutorial you can use your own Terraform configuration in a GitHub repository, or use this example repository. The example repository’s GitHub Actions include a workflow that creates the AWS resources defined in the repository. Whenever the repository trigger event happens on the main branch, it runs the workflow defined in the .github/workflows
directory. It then performs the infrastructure creation or management in AWS. The figure below outlines the interaction between the GitHub repository, Actions, HCP Terraform, and AWS.
Here’s how to implement this workflow.
» Prerequisites
Ensure you have the following:
- An AWS account with necessary permissions to create resources.
- A HCP Terraform account, with a workspace set up for this tutorial.
- A GitHub account, with a repository for Terraform configuration files.
- The Terraform CLI installed on your local machine for testing purposes.
Follow the following steps below to add AWS credentials in the HCP Terraform workspace and the TF_API_TOKEN
in GitHub Actions secrets.
» Securely access AWS from HCP Terraform
HCP Terraform’s dynamic provider credentials allow Terraform runs to assume an IAM role through native OpenID Connect (OIDC) integration and obtain temporary security credentials for each run. These AWS credentials allow you to call AWS APIs that the IAM role has access to at runtime. These credentials are usable for only one hour by default, so their usefulness to an attacker is limited.
For more on how to securely access AWS from HCP Terraform with OIDC federation, check out the Access AWS from HCP Terraform with OIDC federation blog.
» Add HCP Terraform token to GitHub Actions
Fetch the TF_API_TOKEN
by following instructions available in the HCP Terraform documentation. This example creates a user API token for the GitHub Action workflow.
Open the GitHub repository with the Terraform configuration.
Click on "Settings" in the repository menu. From the left sidebar, select "Secrets" and then choose "Actions".
To add a new repository secret, click on "New repository secret". Name the secret TF_API_TOKEN
and add the HCP Terraform API token to the “Value” field. Click "Add secret" to save the new secret.
By following these steps, you will securely provide your AWS credentials to HCP Terraform and also provide the HCP Terraform API token to GitHub Actions, enabling automated infrastructure deployment through a GitHub Actions workflow.
» Create the GitHub Actions workflow
After setting up the credentials, add a GitHub Actions workflow to your repository. The example repository uses a workflow YAML file defining one job with four steps to initialize, plan, apply, and destroy Terraform. This workflow uses the HashiCorp official marketplace actions for performing the Terraform command operations.
# This workflow will create AWS resource using HCP Terraform
# It is reusable workflow that can be called in other workflows
name: AWS Infra Creation Using in HCP Terraform
on:
workflow_call:
secrets:
TF_API_TOKEN:
required: true
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
env:
tfcode_path: tfcloud_samples/amazon_ec2
tfc_organisation: demo-tf-org # Replace it with your TFC Org
tfc_hostname: app.terraform.io
tfc_workspace: demo-tf-workspace # Replace it with your TFC Workspace
jobs:
aws_tfc_job:
name: Create AWS Infra Using TFC
runs-on: ubuntu-latest
steps:
- name: Checkout tf code in runner environment
uses: actions/checkout@v3.5.2
# Configure HCP Terraform API token, since we are using remote backend option of HCP Terraform in AWS code
- name: Setup Terraform CLI
uses: hashicorp/setup-terraform@v2.0.2
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
# Add the AWS Creds as ENV variable in HCP Terraform workspace, since the tf run happens in HCP Terraform environment
# Invoke the Terraform commands
- name: Terraform init and validate
run: |
echo `pwd`
echo "** Running Terraform Init**"
terraform init
echo "** Running Terraform Validate**"
terraform validate
working-directory: ${{ env.tfcode_path }}
- name: Terraform Plan
uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.0
id: run
with:
workspace: ${{ env.tfc_workspace }}
plan_only: true
message: "Plan Run from GitHub Actions"
## Can specify hostname,token,organization as direct inputs
hostname: ${{ env.tfc_hostname }}
token: ${{ secrets.TF_API_TOKEN }}
organization: ${{ env.tfc_organisation }}
- name: Terraform Plan Output
uses: hashicorp/tfc-workflows-github/actions/plan-output@v1.3.0
id: plan-output
with:
hostname: ${{ env.tfc_hostname }}
token: ${{ secrets.TF_API_TOKEN }}
organization: ${{ env.tfc_organisation }}
plan: ${{ steps.run.outputs.plan_id }}
- name: Reference Plan Output
run: |
echo "Plan status: ${{ steps.plan-output.outputs.plan_status }}"
echo "Resources to Add: ${{ steps.plan-output.outputs.add }}"
echo "Resources to Change: ${{ steps.plan-output.outputs.change }}"
echo "Resources to Destroy: ${{ steps.plan-output.outputs.destroy }}"
# Once the user verifies the Terraform Plan, the user can run the Terraform Apply and Destroy commands
apply_terraform_plan:
needs: aws_tfc_job
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.5.2
- name: Setup Terraform CLI
uses: hashicorp/setup-terraform@v2.0.2
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
# Invoke the Terraform commands
- name: Terraform init and validate
run: |
echo `pwd`
echo "** Running Terraform Init**"
terraform init
echo "** Running Terraform Validate**"
terraform validate
working-directory: ${{ env.tfcode_path }}
- name: Terraform Apply
run: echo "** Running Terraform Apply**"; terraform apply -auto-approve
working-directory: ${{ env.tfcode_path }}
- name: Terraform Destroy
run: echo "** Running Terraform Destroy**"; terraform destroy -auto-approve
working-directory: ${{ env.tfcode_path }}
Let’s review each section of the workflow.
» Define the triggers
When you push commits or open a pull request on the main
branch, the workflow initializes, plans, and applies Terraform. This workflow can be triggered by a:
-
workflow_call
: This allows the workflow to be reused in other workflows. It requires theTF_API_TOKEN
secret. -
push
: Triggers the workflow when there is a push to themain
branch. -
pull_request
: Triggers the workflow when a pull request is made to themain
branch. -
workflow_dispatch
: Allows the GitHub Actions interface to manually trigger the workflow.
# This workflow will create AWS resource using HCP Terraform
# It is reusable workflow that can be called in other workflows
name: AWS Infra Creation Using in HCP Terraform
on:
workflow_call:
secrets:
TF_API_TOKEN:
required: true
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
» Configure environment variables
Set the tfcode_path
environment variable to specify the location of your Terraform configuration files within the repository. Include the HCP Terraform organization, workspace, and hostname for future jobs.
env:
tfcode_path: tfcloud_samples/amazon_ec2 # Directory in which the tf files are stored
tfc_organisation: demo-tf-org # Replace it with your HCP Terraform Org
tfc_hostname: app.terraform.io
tfc_workspace: demo-tf-workspace # Replace it with your HCP Terraform Workspace
» Define jobs
In the GitHub Actions workflow, define each automation step inside the jobs
block. The job consists of job name and workflow runner instance definition in the runs-on
block. This workflow uses the ubuntu-latest
instance runner.
The first step in the apply_terraform_plan
is the actions/checkout
entry, which clones the repository into the GitHub Actions runner. This makes the Terraform configuration files available for subsequent steps.
jobs:
aws_tfc_job:
name: Create AWS Infra Using HCPTF
runs-on: ubuntu-latest
steps:
- name: Checkout tf code in runner environment
uses: actions/checkout@v3.5.2
In the second step, use the hashicorp/setup-terraform
pre-built action to configure the Terraform CLI in the runner environment. It sets up the HCP Terraform API token (TF_API_TOKEN
) for authentication. This token allows the Terraform CLI to communicate with HCP Terraform, enabling it to manage state, perform operations, and apply configurations in the context of the HCP Terraform workspace.
- name: Setup Terraform CLI
uses: hashicorp/setup-terraform@v2.0.2
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
Next, initialize and validate Terraform. terraform init
initializes the Terraform working directory by downloading necessary providers and initializing backends. If you’re using HCP Terraform as the backend, this command configures the workspace and prepares it for operations.
terraform validate
checks the syntax and validity of the Terraform files, ensuring they are correctly formatted and logically sound. These commands run inside the working directory defined by env.tfcode_path
, which contains the Terraform configuration.
- name: Terraform init and validate
run: |
echo `pwd`
echo "** Running Terraform Init**"
terraform init
echo "** Running Terraform Validate**"
terraform validate
working-directory: ${{ env.tfcode_path }}
In general, you will want to review the terraform plan
before applying to verify the changes Terraform will make. Use the hashicorp/tfc-workflows-github/actions/create-run
action to run a plan in HCP Terraform and export the plan using the plan_only
attribute. Then, use the hashicorp/tfc-workflows-github/actions/plan-output
action to get the output of the Terraform plan using the plan_id
from the previous step's output. Finally, print the plan status and resource changes in the workflow’s output.
- name: Terraform Plan
uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.0
id: run
with:
workspace: ${{ env.tfc_workspace }}
plan_only: true
message: "Plan Run from GitHub Actions"
hostname: ${{ env.tfc_hostname }}
token: ${{ secrets.TF_API_TOKEN }}
organization: ${{ env.tfc_organisation }}
- name: Terraform Plan Output
uses: hashicorp/tfc-workflows-github/actions/plan-output@v1.3.0
id: plan-output
with:
hostname: ${{ env.tfc_hostname }}
token: ${{ secrets.TF_API_TOKEN }}
organization: ${{ env.tfc_organisation }}
plan: ${{ steps.run.outputs.plan_id }}
- name: Reference Plan Output
run: |
echo "Plan status: ${{ steps.plan-output.outputs.plan_status }}"
echo "Resources to Add: ${{ steps.plan-output.outputs.add }}"
echo "Resources to Change: ${{ steps.plan-output.outputs.change }}"
echo "Resources to Destroy: ${{ steps.plan-output.outputs.destroy }}"
The workflow outputs the plan status and any resources to add, change, or destroy in its log.
If the plan does not reflect the correct changes, fix the Terraform configuration to achieve the expected output. After verifying the plan in the previous job, manually trigger the workflow to run terraform apply
. This command starts a run in the HCP Terraform workspace, where the actual infrastructure changes are made.
apply_terraform_plan:
needs: aws_tfc_job
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.5.2
- name: Setup Terraform CLI
uses: hashicorp/setup-terraform@v2.0.2
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
# Invoke the Terraform commands
- name: Terraform init and validate
run: |
echo `pwd`
echo "** Running Terraform Init**"
terraform init
echo "** Running Terraform Validate**"
terraform validate
working-directory: ${{ env.tfcode_path }}
- name: Terraform Apply
run: echo "** Running Terraform Apply**"; terraform apply -auto-approve
working-directory: ${{ env.tfcode_path }}
Optionally, you can add a terraform destroy
step to clean up resources and avoid unnecessary costs. You can add this step in non-production or testing environments.
- name: Terraform Destroy
run: |
echo "** Running Terraform Destroy**"
terraform destroy -auto-approve
working-directory: ${{ env.tfcode_path }}
» Setup review and further learning
This setup leverages the strengths of both platforms: GitHub Actions for CI/CD automation and HCP Terraform for secure, collaborative infrastructure management. Integrating HCP Terraform with GitHub Actions provides a powerful, automated pipeline for deploying and managing AWS infrastructure. By leveraging these tools, teams can achieve more reliable and efficient infrastructure management, reduce manual errors, and ensure consistency across environments.
HCP Terraform facilitates collaboration among team members ensuring that infrastructure changes are managed safely and efficiently. Platform teams can audit the runs in HCP Terraform while development teams can review runs in GitHub Actions.
From a security perspective, HCP Terraform workspaces can be configured with environment variables, such as AWS credentials, or dynamic credentials. The GitHub Actions workflow does not directly handle the credentials, which minimizes the blast radius of compromised credentials through the workflow. HCP Terraform provides additional features like access controls, private module registry, and policy enforcement to ensure that infrastructure changes are secure and compliant with organizational policies.
This guide has walked you through setting up a basic workflow, but the flexibility of both platforms allows for customization to fit your specific needs.
For further questions on best practices, please refer to the GitHub Actions and HCP Terraform FAQs available in this repository. As mentioned before, this repository includes the full code example used in this post. For more information on GitHub Actions, review GitHub’s documentation. To learn more about automating Terraform with GitHub Actions, review the official tutorial on the HashiCorp Developer portal and the starter workflow templates to use HCP Terraform with GitHub Actions.
For alternatives to this workflow, you can also use HCP Vault Secrets to sync the TF_API_TOKEN
to the GitHub Actions secrets. With this method, you only need to update the token in one place, rather than every GitHub repo. HashiCorp also has integration templates for HCP Terraform and GitHub Actions that you should check out.
Sign up for the latest HashiCorp news
More blog posts like this one
Fix the developers vs. security conflict by shifting further left
Resolve the friction between dev and security teams with platform-led workflows that make cloud security seamless and scalable.
HashiCorp at AWS re:Invent: Your blueprint to cloud success
If you’re attending AWS re:Invent in Las Vegas, Dec. 2 - Dec. 6th, visit us for breakout sessions, expert talks, and product demos to learn how to take a unified approach to Infrastructure and Security Lifecycle Management.
Speed up app delivery with automated cancellation of plan-only Terraform runs
Automatic cancellation of plan-only runs allows customers to easily cancel any unfinished runs for outdated commits to speed up application delivery.