Automating Terraform Infrastructure Using GitHub Actions

Nick Sanders
4 min readJan 31, 2023

--

In my previous article,I showed how you can provision a static website using AWS and Terraform. In this post, I will show you how to automatically deploy your infrastructure using GitHub Actions.

What is GitHub Actions

GitHub Actions is a CI/CD platform that allows developers to automate build, test, and deploy stages. You can configure a pipeline to run on a specific events such as a “push” to the repository. The workflow file is in .yml format that should be stored in the “.github/workflows” file of your repository.

For this tutorial, you will need your AWS access keys and a Terraform API token. To create an API token, go to app.terraform.io and login or create an account. Once logged in, click the user icon in the top left corner and click “User Settings”. Next, click “Tokens” then “Create an API token”. Name your token the click “create”. Make sure to store your API key in a safe location as you won’t be able to access it again.

Navigate to your GitHub repository and under “Settings”, click “Actions”

This is where you will import repository secrets which are essentially variables to be used in workflow files. The secrets we’ll be importing are your AWS_ACCESS_KEY, AWS_SECRET_KEY, AWS_REGION, and TF_API_TOKEN.

Creating ‘Terraform Apply’ workflow

We begin by naming our workflow file, stating the trigger which it should be ran on and which branch.

name: Terraform-Apply

on:
push:
branches: [ "master" ]

Next, we’ll list the jobs that will be run in our workflow. A job is a set of steps that run when an event is triggered. Each step is either a shell script or an action that will be ran. An action is a custom application that performs complex tasks, reducing the amount of code you have to write. We will name our job “Terraform” and it will run on “ubuntu-latest” in the production environment. The “runs-on” prompt specifies which virtual machine your workflow will run on. GitHub provides Ubuntu Linux, Microsoft Windows, and macOS runners. You are able to provide your own runner if you so choose.

name: Terraform-Apply

on:
push:
branches: [ "master" ]
jobs:
terraform:
name: Terraform
runs-on: ubuntu-latest
environment: production
defaults:
run:
working-directory: terraform

Next, we’ll import the variables needed to connect with the AWS CLI.

name: Terraform-Apply

on:
push:
branches: [ "master" ]

jobs:
terraform:
name: Terraform
runs-on: ubuntu-latest
environment: production
defaults:
run:
working-directory: terraform
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }}
AWS_REGION: 'us-east-1'

Now we’ll begin writing the steps for our action. We will use the “actions/checkout@v3” to checkout the repository onto our runner. The “aws-actions/configure-aws-credentials@v1” step will configure the AWS CLI with our credentials.

name: Terraform-Apply

on:
push:
branches: [ "master" ]

jobs:
terraform:
name: Terraform
runs-on: ubuntu-latest
environment: production
defaults:
run:
working-directory: terraform
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }}
AWS_REGION: 'us-east-1'

permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v3
- name: set AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ secrets.AWS_REGION }}

Finally, we’ll configure Terraform’s CLI using the “hashicorp/setup-terraform@v1” action. The steps we’ll run for our Terraform job will be:

  • terraform init — to initialize our infrastructure
  • terraform fmt — to format our code correctly
  • terraform apply — to build our infrastructure
name: Terraform-Apply

on:
push:
branches: [ "master" ]

jobs:
terraform:
name: Terraform
runs-on: ubuntu-latest
environment: production
defaults:
run:
working-directory: terraform
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }}
AWS_REGION: 'us-east-1'

permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v3
- name: set AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ secrets.AWS_REGION }}

- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

- name: Terraform Init
id: init
run: terraform init

- name: Terraform Format
id: fmt
run: terraform fmt

- name: Terraform Apply
if: github.event_name == 'pull_request'
run: terraform apply -auto-approve

Updating site content

Next, we’ll create a workflow that will update our site content on push requests.

The file looks very similar:

name: Upload Website 

on:
push:
branches:
- master

jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3
- name: Set AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.SECRET_KEY }}
aws-region: us-east-1
- name: S3 copy
run: aws s3 sync ./site_files/. s3://nicksands.link --delete

The job checks out the repository onto the runner and configures the AWS credentials. The “sync” command copies files from the specified directory into the S3 bucket. The “delete” command deletes the files previously stored in the bucket.

Congratulations! You have created a website’s infrastructure using Terraform and automated the deployment using the CI/CD tool GitHub Actions. These tools used in unison is a powerful method to quickly deploy infrastructure and make changes with limited downtime.

--

--