In this article, I will continue the discussion from this section and present one way how we can automate our infrastructure and application deployments using Terraform Cloud and GitHub Actions.
As it was mentioned in the other article, we will suppose that our system is composed of the following modules:
- A Node.js Express API.
- Two React clients.
- One PostgreSQL database.
- Our infrastructure code (written in Terraform).
What tools do we need?
We will be using Terraform Cloud as our remote backend for storing our infrastructure state (but not only), and GitHub Actions (together with AWS plugins) for the CI/CD pipeline.
- Terraform Cloud: It manages Terraform runs in a consistent and reliable environment, and includes easy access to shared state and secret data, access controls for approving changes to infrastructure, a private registry for sharing Terraform modules, detailed policy controls for governing the contents of Terraform configurations, and more.
- GitHub Actions: Makes it easy to automate all your software workflows (build, test, and deploy your code right from GitHub).
- AWS for GitHub Actions: Official AWS plugins for GitHub Actions.
Terraform Cloud setup
For setting up Terraform Cloud we will go through the following steps:
- Create your Terraform Cloud account.
- Create workspaces.
- Define environment and Terraform variables.
- Define the working directory.
Step 1: Sign up
To create your Terraform Cloud account, follow this link. At the moment of writing, Terraform Cloud offers a free plan with all the open-source features, as well as state management, remote operations, and private module registries.
After successfully creating and confirming your account, you can log in. You will be redirected to the "Workspaces" view.
Step 2: Create workspaces
In the next step, we will create our three workspaces, one per environment. To create a workspace in Terraform Cloud, click the "New workspace" button.
When the workspace creation form shows up, we can choose the "API-driven workflow", since we will be triggering our deployments using GitHub Actions.
In the next step, we must set a name and a description for our workflow.
Step 3: Set variables
After creating the workspace, we must define the environment variables (the AWS access key and secret key), and the Terraform variables for our development environment (what we have defined in our variables.tf file within the development directory). We can click on the created workspace and navigate to the "Variables" tab to add variables.
Step 4: Define the working directory
As a final step, we need to set the working directory of our workspace, so that Terraform can go within this specified directory and execute all commands.
Step 5: Create two other workspaces for staging and production
Finally, we can repeat all the steps above for creating two new workspaces for the remaining environments.
Preparing the GitHub workflow
Our GitHub Actions workflow will contain the following steps:
The whole process will be divided into two main steps:
- Deploying the infrastructure: This step will be composed of multiple smaller jobs (lint, init, plan, update pull-request, apply, output).
- Deploying the applications: There are three jobs executed in parallel in this phase (building and deploying the API, building and deploying the first web client, building and deploying the second web client). Each of these jobs is composed of other smaller jobs.
Step 1: Adding the necessary keys to our GitHub repo
Before going into each job in detail, we need to add our configuration secrets to our GitHub repo. We need the following variables added as secrets:
- AWS Access Key Id, AWS Secret Access Key, AWS Region: These are all needed to authenticate the AWS plugins we will use during our pipeline execution (for pushing our images or deploying our frontend applications). We can generate the keys in the AWS IAM users section (keep in mind to give the CI/CD user only the necessary permissions).
- Terraform API key: For deploying the infrastructure using GitHub Actions we must first set up Terraform on our runner. For this, we need to generate an API token on our Terraform Cloud account, under the "Tokens" tab in user settings.
Here is the "Secrets" view after adding all the necessary keys:
Step 2: Deploying the infrastructure
For deploying our infrastructure, we will execute the steps listed below (some of the steps are executed only when doing pull requests, others when pushing to specific branches). We will be looking at the development workflow from this point onwards. For more details regarding this step, here is the official documentation.
- Define the working directory: For the development workflow the working directory will be
infrastructure/development. On the provider.tf file under this directory, we have configured Terraform Cloud as the backend and we have also defined the workspace name for this environment.
- Setup Terraform: Retrieves the Terraform CLI used in the GitHub action workflow, reads the Terraform API token from the secret variables, and authenticates the CLI to Terraform Cloud.
- Terraform Format: Checks whether the configuration has been properly formatted. If the configuration isn't properly formatted this step will produce an error.
- Terraform Init: Initializes the configuration used in the GitHub action workflow.
- Terraform Plan: Generates a Terraform plan. Since provider.tf configures the Terraform Cloud integration, this step triggers a remote plan run in the Terraform Cloud. This step only runs on pull requests and continues running even if an error is thrown. The error will be caught and handled in the following step.
- Update Pull Request: Adds a comment to the pull request with the results of the format, init and plan steps. In addition, it displays the "plan" output (
steps.plan.outputs.stdout). This allows your team to review the results of the plan directly in the PR instead of opening Terraform Cloud. This step only runs on pull requests.
- Terraform Plan Status: Returns whether a plan was successfully generated or not. This is where the "plan" errors are caught. If an error happened, the process exits with status 1.
- Terraform Apply: Applies the configuration. This step will only run when a commit is pushed to one of our main branches (development, staging, production).
- Terraform Output: As a final step, we need to output a few variables which will be useful for the next steps in our pipeline.
Here is what our development workflow script contains till this point:
Step 3: Building and deploying the API
Our API is deployed in ECS containers. These are the steps we need to follow to build and push our image to the registry (after that, the new image is read automatically and the containers are recreated):
- Configure AWS Credentials: Using the AWS Access Key and Secret Key stored in our secrets, we authenticate the AWS plugins that are used in this step.
- Login to Amazon ECR: We use a plugin to log in to our container registry where we will push the generated image.
- Build, tag, and push the image to Amazon ECR: This step builds the docker image for the API, tags it with the "latest" tag, and pushes it to ECR.
Here is how the whole step looks:
Step 4: Building and deploying the Web clients
Our two web clients are built in React.js and hosted in S3 buckets. To build and deploy them, we execute the following steps:
- Configure AWS Credentials: Same as above.
- Build React App: Here we install all project dependencies, configure the local environment (e.g. define the API URL which the web client will consume), and build the application.
- Deploy app build to S3 bucket: In the last step, we push the generated build to the corresponding S3 bucket.
Here is how the code looks (for both web clients):
Testing the setup
For testing the whole setup, we can do the following:
- Implement a quick dummy feature in a feature branch (e.g.
- Do a pull request in
Here are some screenshots of different test scenarios:
- Full source code implementation: https://github.com/zeroabsolute/Terraform-AWS-ECS
- Automate Terraform: https://learn.hashicorp.com/collections/terraform/automation
- AWS for GitHub Actions: https://github.com/aws-actions
- GitHub Actions documentation: https://docs.github.com/en/actions