Bootstrapping your Terraform automation with Amazon CodeCatalyst
A walk-through of how to set up Terraform with Amazon CodeCatalyst from scratch to create a CI/CD pipeline for your infrastructure
"Good intentions never work, you need good mechanisms to make anything happen."
- Using S3 as a backend for Terraform state files, with DynamoDB for locking, and encrypting the state file at rest with KMS
- CodeCatalyst to run our CI/CD pipelines to create and update all your infrastructure
About | |
---|---|
✅ AWS experience | 200 - Intermediate |
⏱ Time to complete | 30 minutes |
💰 Cost to complete | Free tier eligible |
🧩 Prerequisites | - AWS Account - CodeCatalyst Account - Terraform 1.3.7+ - (Optional) GitHub account |
💻 Code Sample | Code sample used in tutorial on GitHub |
📢 Feedback | Any feedback, issues, or just a 👍 / 👎 ? |
⏰ Last Updated | 2023-02-22 |
- Clicking in the console to set everything up, aka "ClickOps"
- Using a CLI to create the resources for you with scripts, "Procedural"
- Using Terraform without storing the state file to bootstrap, then add in the state file configurations to store it
Create Space
on the CodeCatalyst Dashboard, add a name (we will use Terraform CodeCatalyst
), add the AWS Account ID to link to for billing (111122223333
is a placeholder), you can find your account ID in the top right of your AWS Console, and follow the prompts to link your AWS Account with CodeCatalyst.
Create Project
button, select Start from scratch
, and give your project a name - we will use TerraformCodeCatalyst
.
Code
in the left-side navigation menu, then on Source repositories
, Add repository
and choose Create repository
. Set a repository name (we will use bootstrapping-terraform-automation-for-amazon-codecatalyst
in this tutorial), add a description, and Terraform
for the .gitignore file
:
CI/CD
, then on Environments
, and then Create environment
. Add the Environment name
, Description
, choose your AWS account from the dropdown under AWS account connection
, and click Create environment
.
Dev Environment
under Code
, then Create Dev Environment
, select Cloud9
- this tutorial with use Cloud9. Select Clone a repository
, select bootstrapping-terraform-automation-for-amazon-code-catalyst
in the dropdown for Repository
, add an Alias
of TerraformBootstrap
, and then click the Create
button.

terraform --version
. This tutorial uses version 1.3.7, to ensure you are using that version, use the following commands:Access key ID
and Secret access key
values, then run aws configure
in the terminal of your dev environment (you can leave the last two default values blank, or enter values you prefer):aws sts get-caller-identity
in the terminal:- IAM roles: Provides the role for our workflow to assume in the account - one for the
main
branch, one for any pull requests (PRs). - IAM policies: Set the boundaries of what the workflow IAM roles may do in our account - full admin access for
main
branch allowing creation of infrastructure,ReadOnly
for the PR branches to allow validating any changes. - S3 bucket: An S3 bucket to store our Terraform state file in.
- S3 bucket versioning: Allows keeping backup copies of the Terraform state file each time it changes.
- DynamoDB Table: Used by Terraform to create a lock while running - this prevents multiple CI jobs making changes when run in parallel.
- KMS Encryption Key: (Optional) While the state file is stored in S3, we want to encrypt it while at rest using a KMS key. For this tutorial, we will use the pre-existing
aws/s3
key, if you prefer to use a different KMS key ($1/month/key), there will be a section below to describe how to make changes to do that.
_bootstrap/variable.tf
file and update the state_file_bucket_name
(S3 bucket names are globally unique), and optionally the state_file_lock_table_name
variables with the values for your S3 bucket name for the state file, DynamoDB table name for locks, and optionally change the aws_region
if you want to use a different region.terraform plan
command has been omitted using ...
):plan
command will output a list of resources to create, and you can take a look at exactly what it will create. Once you are satisfied, run terraform apply
, and confirm the infrastructure creation._bootstrap/terraform.tf
with the following, and update the bucket
and region
values with your values:region
and state_file_bucket
variables in the Terraform backend configuration, but it does not allow any variable / local interpolation.terraform init -migrate-state
, and you should see the following output:git add .
, git commit -m "Terraform bootstrapped"
and git push
:main
branch with permissions to create resources, and another for all pull requests with read-only permissions. We need to add these to our CodeCatalyst Space. In the top left of the page, click on the Space dropdown, and then click on your Space name. Navigate to the AWS accounts
tab, click your AWS account number, and then on Manage roles from the AWS Management Console
. This will open a new tab, select Add an existing role you have created in IAM
, and select Main-Branch-Infrastructure
from the dropdown. Click Add role
:
Successfully added IAM role Main-Branch-Infrastructure.
banner at the top. Click on Add IAM role
, and follow the same process to add the PR-Branch-Infrastructure
role. Once done, you can close this window and go back to the CodeCatalyst one.terraform.tf
, with the following content - take note that the key
for the bucket is different from what we used for the bootstrapping infrastructure, and as before, replace the bucket
, region
, dynamodb_table
, and kms_key_id
with your values:region
set in the above block indicates in which region the S3 bucket was created, not where we will create our resources. We also need to configure the AWS
provider, and set the region
to use. Will use a variable for this, you could also hard-code it, but it is more manageable to keep all the variables in a single variables.tf
file for this purpose. Create providers.tf
with the following content:variables.tf
file with (you can change the region here if you want to create resources in a different one):.codecatalyst/workflows/main_branch.yml
in your IDE, and add the following - remember to replace the placeholder AWS account ID 111122223333
with the value of your account, and the IAM role names if you changed them (you can choose between the standard CodeCatalyst workflow, or to use GitHub Actions with CodeCatalyst):main
branch - this is needed as only workflows committed to the repo will be run by CodeCatalyst. Use the following commands:CI/CD
-> Workflows
page. You should see the workflow running:
Recent runs
to expand it, you will see the details of the currently running job. Click on the job ID (Run-XXXXX
) to view the different stages of the build:
main
branch workflow done, it is time to set up the pull request one. The workflow will be very similar as the main
branch one, with the following difference:- A different workflow name -
TerraformPRBranch
- We use the
PR-Branch-Infrastructure
IAM role to ensure we cannot make any infrastructure changes in the PR workflow - We remove the
terraform apply
step - The trigger for the build is for when a PR to the
main
branch is opened or updated (REVISION
)
.codecatalyst/workflows/pr_branch.yml
, and add the following (replacing the placeholder AWS account ID of 111122223333
, and the IAM role name if you changed it) - you can choose between the standard CodeCatalyst workflow, or to use GitHub Actions with CodeCatalyst:main
branch before it will trigger for a new PR, so let's do that now:main
branch workflow as we added a change, but without adding any additional Terraform resources, it will not make any changes:
vpc.tf
- we will create a VPC that has three public subnets, and the required routing tables. Add the following content to the file:--set-upstream origin test-pr-workflow
as the remote branch does not yet exist:Code
, then Pull requests
, and click on Create pull request
. Select test-pr-workflow
as the Source branch
, main
as the Destination branch
, and add in a Pull request title
and Pull request description
. You can also preview the changes the PR will make on the bottom of the screen:
Create
, and then navigate to CI/CD
-> Workflows
, and select All branches
from the dropdown in the top of the Workflows
menu. After selecting All branches
, you will see four workflows, the TerraformMainBranch
and TerraformPRBranch
ones, and a copy for each of the two branches main
and test-pr-workflow
. The TerraformMainBranch
workflow will have an error with Workflow is inactive
, which is expected as we limit that workflow to only run on our main branch. Click on the Recent runs
under the TerraformPRBranch
workflow for the test-pr-workflow
branch, and then on Terraform-PR-Branch-Plan
job to see the details.
Terraform Plan
step, you will be able to see the proposed infrastructure changes listed in the output. You can now inspect exactly which changes will be made to your infrastructure from this pull request. In you standard day-to-day operations, you would now go back to pull request to decide what action to take. If the proposed changes have been reviewed and approved, you can merge the pull request, or you can start a conversation on the PR to address any issues or concerns. We will now merge this request to roll out this infrastructure in our account by navigating to Code
-> Pull requests
, clicking on the Title
or ID
of the PR, and then the Merge
button. You are presented with a choice between a Fast forward merge
, or a Squash and merge
option. Fast forward merge
will take all the commits on the branch and add them sequentially to the main
branch as if they were done there. For the Squash merge
, it will combine all the commits on the test-pr-workflow
branch into a single commit before merging that single commit to main
. Which one you use will depend on your development approach, for this tutorial, will use the Fast forward merge
one. You can also select the option to Delete the source branch after merging this pill request. Source branch: test-pr-workflow
, this will help keep your repository clean from too many branches if they are no longer used. Click on Merge
, and navigate to CI/CD
-> Workflows
to see the new VPC being created. Click on the currently running TerraformMainBranch
workflow's Recent runs
, then on the job ID, and then on the 2nd step to see the progress in the right-hand pane. Once the job completes, we can verify that the VPC was created by navigating to the VPC section of the AWS Console, a clicking on the VPC ID
for the VPC with the name CodeCatalyst-Terraform
. You should see something similar:
- Make sure you are on the
main
branch by runninggit checkout main
andgit pull
to ensure you have the latest changes, then runterraform destroy
, and typeyes
to confirm - this will remove the VPC we created - To delete all the bootstrapping resourced, first change into the directory by running
cd _bootstrap
. Before we can delete everything, we need to update our S3 state file bucket. We need to change the lifecycle policy to allow the deletion, and addforce_destroy = true
to also delete all the objects in the bucket. Edit_bootstrap/state_file_resources.tf
, and replace the firstaws_s3_bucket
resource with: - Run
terraform apply
, and accept the changes. - Now run
terraform destroy
, and accept the changes. This will result in two errors since we are deleting the S3 bucket where it tries to store the updated state file, and also the DynamoDB table Terraform uses to store the lock to prevent parallel runs. The output will look similar to this: - Lastly, we need to delete the project we created in CodeCatalyst. In the left-hand navigation, go to
Project settings
, click onDelete project
, and follow the instructions to delete the project.
Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.