Effortless Infrastructure: Mastering Automated Deployments with Terraform and GitHub Actions
Tags: cicd, terraform, aws, githubactions
Published: 2024-04-05 16:23:57

---
title: Effortless Infrastructure: Mastering Automated Deployments with Terraform and GitHub Actions
published: true
date: 2024-01-26 16:39:22 UTC
tags: cicd,terraform,aws,githubactions
canonical_url: 
---

### Introduction

In the earlier [blog post](https://thulasirajkomminar.com/efficient-continuous-deployment-with-github-environments-and-aws-codebuild-9086aef7d913), I showcased the steps to achieve continuous deployment using GitHub Actions and AWS CodeBuild specifically for code deployment. Expanding on those principles, let’s delve into the next phase of our workflow — incorporating infrastructure deployment through Terraform.

![](https://cdn-images-1.medium.com/max/551/1*CFyaoqhfbnwDNHlK4iDzrg.png)
_Deployment workflow_

### Setup Terraform Cloud Workspace:

The GitHub Action you’re about to set up will seamlessly integrate with Terraform Cloud, allowing you to efficiently plan and apply your configurations. Before configuring the Actions workflow, ensure you’ve taken the necessary steps: creating a workspace, incorporating your AWS credentials into your Terraform Cloud workspace, and generating a Terraform Cloud user API token.

#### **Step 1:**

To kickstart the process, initiate a new Terraform Cloud workspace. Navigate to the Projects & Workspaces page, click on _New Workspace_, and opt for the _API-Driven Workflow_ option.

![](https://cdn-images-1.medium.com/max/1024/1*tsAmQCZrOV7PS1tQ4a__HQ.png)

#### **Step 2:**

Provide a name for the workspace and proceed by clicking on the _Create_ button.

![](https://cdn-images-1.medium.com/max/590/1*QG_sH1EubpjwkwEmAu34OA.png)

#### **Step 3:**

Now, it’s time to include your AWS IAM credentials as environmental variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_DEFAULT_REGION`) within the workspace. Navigate to the _Variables_ section in the workspace, click on _Add Variable_, and select _Environment variable_ as the variable category. Terraform Cloud will use these credentials to authenticate to AWS.

![](https://cdn-images-1.medium.com/max/1024/1*P_vMNXFa3teNljhnmm8aBQ.png)

#### **Step 4:**

Now, let’s generate an API token to enable authentication for the Actions workflow in Terraform Cloud. Head to the Tokens page within your Terraform Cloud User Settings. Click on _Create an API token_, provide a description, and then click _Generate token_.

![](https://cdn-images-1.medium.com/max/593/1*oAUdEX83zmxYuZbGSKfnFw.png)

### Set up a GitHub repository:

Establish a GitHub repository dedicated to housing your infrastructure configuration code and the associated GitHub Actions workflow.

#### **Step 1:**

Once you’ve created the repository, proceed to the Settings page. Access the _Secrets and Variables_ menu, specifically selecting _Actions_. Click _New Repository Secret_ and input the Terraform API token generated in the previous step.

![](https://cdn-images-1.medium.com/max/1024/1*t6H3XpnwfEv2Ff5czu24aQ.png)

#### **Step 2:**

Now, it’s time to incorporate the following GitHub Actions workflow.

[**Plan**](https://github.com/komminarlabs/tf-with-github-actions/blob/main/.github/workflows/terraform-plan.yaml)

```yaml
name: 'Terraform'

on:
  pull_request:
    branches:
      - main
    paths:
      - terraform/**
      - modules/**

env:
  TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }}
  TF_WORKING_DIRECTORY: terraform/

permissions:
  contents: read
  pull-requests: write

jobs:
  terraform:
    name: "Plan"
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ env.TF_WORKING_DIRECTORY }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
            cli_config_credentials_token: ${{ env.TF_API_TOKEN }}

      - name: Terraform Init
        id: init
        run: |
          git config --global url."https://oauth2:${{ secrets.GITHUB_TOKEN }}@github.com".insteadOf ssh://git@github.com
          terraform init

      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color
        continue-on-error: true

      - name: Update PR
        uses: actions/github-script@v7
        env:
          PLAN_OUTPUT: ${{ steps.plan.outputs.stdout }}
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            // 1. Retrieve existing bot comments for the PR
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            })
            const botComment = comments.find(comment => {
              return comment.user.type === 'Bot' && comment.body.includes('Terraform Plan Results')
            })

            // 2. Prepare format of the comment
            const output = `### Terraform Plan Results 🚀
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
            <details><summary>Validation Output</summary>

            \`\`\`\n
            ${{ steps.validate.outputs.stdout }}
            \`\`\`

            </details>

            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

            <details><summary>Show Plan</summary>

            \`\`\`\n
            ${process.env.PLAN_OUTPUT}
            \`\`\`

            </details>

            *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.TF_WORKING_DIRECTORY }}\`, Workflow: \`${{ github.workflow }}\`*`;

            // 3. If we have a comment, update it, otherwise create a new one
            if (botComment) {
              github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: output
              })
            } else {
              github.rest.issues.createComment({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body: output
              })
            }
```

Now, let’s delve into the deployment job’s steps. We’ve included four key actions:

1. **Check out code:** I’m utilizing a GitHub action to retrieve the code from the repository.
2. **Terraform Init:** This step utilizes the `hashicorp/setup-terraform@v3` action to initialize Terraform. I included the Git configuration in case the code involves private GitHub modules.
3. **Terraform Validate:** This step is dedicated to validating the Terraform code.
4. **Terraform Plan:** This step is employed to execute the Terraform plan.
5. **Update PR:** This step leverages the actions/github-script@v7 action to update the Pull Request with the results from the preceding actions.

[**Apply**](https://github.com/komminarlabs/tf-with-github-actions/blob/main/.github/workflows/terraform-apply.yaml)

```yaml
name: 'Terraform'

on:
  push:
    branches:
      - main
    paths:
      - terraform/**
      - modules/**

env:
  TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }}
  TF_WORKING_DIRECTORY: terraform/

permissions:
  contents: read

jobs:
  terraform:
    name: "Apply"
    runs-on: ubuntu-latest
    environment: prd
    defaults:
      run:
        working-directory: ${{ env.TF_WORKING_DIRECTORY }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
            cli_config_credentials_token: ${{ env.TF_API_TOKEN }}

      - name: Terraform Init
        id: init
        run: |
          git config --global url."https://oauth2:${{ secrets.GITHUB_TOKEN }}@github.com".insteadOf ssh://git@github.com
          terraform init

      - name: Terraform Apply
        id: apply
        run: terraform apply -auto-approve -no-color
```

Let’s delve into the steps outlined in this workflow.

1. **Check out code:** I’m utilizing a GitHub action to retrieve the code from the repository.
2. **Terraform Init:** This step utilizes the `hashicorp/setup-terraform@v3` action to initialize Terraform. Additionally, I’ve included Git configuration in case the code incorporates private GitHub modules. Executing this action is a prerequisite before proceeding with the apply step.
3. **Terraform Apply:** This step applies the Terraform infrastructure configuration to AWS.

### Demo

Now, let’s put our setup into action. For demonstration purposes, I’ve included Terraform configuration files to create an S3 bucket.

#### **Pull Request**

After incorporating the Terraform configuration files into a feature branch, let’s create a pull request to the main branch. This action will initiate GitHub Actions to execute Validation and Plan, with the results seamlessly added back to the pull request for convenient review.

![](https://cdn-images-1.medium.com/max/937/1*IbD012o9Yh60jqMQqroZKg.png)
_Pull Request showcasing Terraform Plan Results_

![](https://cdn-images-1.medium.com/max/821/1*HP0-Ts_9L1FkciWB2I1rNA.png)
_Thorough Examination of Terraform Plan Details_

![](https://cdn-images-1.medium.com/max/1024/1*LiR_GIt-70pewVMYvntoXg.png)

#### Merge

After a thorough review of the pull request and ensuring the Terraform plan results meet expectations, proceed to merge the pull request. This will trigger the Terraform apply process.

![](https://cdn-images-1.medium.com/max/1024/1*RQVfpIZc-TSP7MBhz-VsIQ.png)
_Production Deployment_

![](https://cdn-images-1.medium.com/max/800/1*0wi-k-5NmQtG3KH68B-oVg.png)
_In-Depth Exploration of the Terraform Workspace_

### Wrapping it up

In conclusion, we’ve explored the powerful capabilities of GitHub Actions in automating Terraform infrastructure deployments. If you’ve already followed my [previous blog](https://thulasirajkomminar.com/efficient-continuous-deployment-with-github-environments-and-aws-codebuild-9086aef7d913) on code deployment using GitHub Actions, you can seamlessly combine these processes to achieve sequential infrastructure deployment and code deployment, streamlining your development workflow.

* * *