Efficient Continuous Deployment with GitHub Environments and AWS CodeBuild
Introduction
Continuous Deployment (CD) is a methodology that leverages automation for releasing software updates efficiently. In a standard CD setup, code undergoes automatic building and testing phases prior to its deployment. With GitHub Actions, automating these software workflows becomes seamless. Configure your CD pipeline to initiate whenever there’s a GitHub event, such as a new code push or a pull request. For a comprehensive list of event triggers, click here.
Environments define common deployment targets such as production
, staging
, or development
. They can be set up with protection rules and associated secrets. When a workflow job references an environment, it only begins after satisfying all the environment’s protection rules. Moreover, a job can only access the secrets linked to an environment after all its deployment protection criteria are met.
In this blog post, I’ll guide you through setting up a Continuous Deployment pipeline using GitHub Actions and environments, focusing on both development
and production
stages.
Initial setup
GitHub
Environments:
First, we’ll establish the development
and production
environments and implement deployment protection rules for added security.
Navigate to the repository where you intend to set up the Continuous Deployment (CD). Click on Settings, and then select Environments from the sidebar. To add environments, simply click on the New Environment button. As depicted in the image below, I have established two environments.
After adding an environment, you’ll be prompted to configure its settings. Let’s dive into that. We’ll start by setting up a protection rule for the production
environment. As illustrated below, I’ve added a required reviewer to manually approve all deployments headed to production.
Next, we’ll restrict deployments to this environment based on specific branch naming patterns. As demonstrated below, I selected the Selected branches and tags rule. Click on Add deployment branch or tag rule, and in the subsequent popup, select Ref type as branch and enter main
for the Name pattern.
To deploy to an AWS account, we require the AWS account ID and region. Therefore, I’ve added these as environment secrets, along with a variable to capture the environment name.
Likewise, I’ve configured deployment restrictions, environment secrets, and variables for the development
environment. However, I omitted the reviewer setup since this environment is solely for development deployments.
Actions:
Now, let’s proceed to create GitHub workflows so that deployments are automatically triggered whenever a commit is made to either the develop
or main
branch.
name: CD - Production
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
codebuild:
runs-on: ubuntu-latest
environment: Production
steps:
- name: echo
run: echo 'Deploying to ${{ vars.ENV }}'
- name: Check out code
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNTID }}:role/GitHubTriggerRole
aws-region: ${{ secrets.AWS_REGION }}
- name: Run CodeBuild
uses: aws-actions/aws-codebuild-run-build@v1
with:
project-name: CD-CodeBuild
name: CD - Development
on:
push:
branches:
- develop
permissions:
id-token: write
contents: read
jobs:
codebuild:
runs-on: ubuntu-latest
environment: Development
steps:
- name: echo
run: echo 'Deploying to ${{ vars.ENV }}'
- name: Check out code
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNTID }}:role/GitHubTriggerRole
aws-region: ${{ secrets.AWS_REGION }}
- name: Run CodeBuild
uses: aws-actions/aws-codebuild-run-build@v1
with:
project-name: CD-CodeBuild
Now, let’s delve into the deployment job’s steps. We’ve included four key actions:
1. Echo:
This step simply prints the name of the environment being deployed.
2. Check out code:
I’m utilizing a GitHub action to retrieve the code from the repository.
3. Configure AWS Credentials:
In this step, I’ve set up an OIDC (OpenID Connect) configuration to enable role assumption, allowing me to make AWS service calls on behalf of the environment. I’ll provide a more detailed explanation of the OIDC setup in the next section, specifically focusing on AWS.
4. Run CodeBuild:
This final step initiates an AWS CodeBuild project and waits for its status, ensuring a smooth and coordinated deployment process.
Note: Ensure that the
environment
name in the actions file corresponds with the GitHub repository’s environment name.
AWS
Now, let’s explore what needs to be configured on the AWS side.
IAM
We need to set up two roles: one for GitHub Actions to assume using OIDC, and another for the CodeBuild project to assume.
OIDC:
To begin, log in to the AWS console and navigate to the IAM service. In the sidebar, select Identity providers and click on Add Provider. In the Configure provider window, choose the OpenID Connect option.
Here are the specific details you’ll need to fill in:
- For the Provider URL, use
https://token.actions.githubusercontent.com.
- For the Audience, enter
sts.amazonaws.com
. - Finally, click Add Provider to complete the setup.
More details on this can be found here.
Create IAM Roles:
1. GitHub role:
Now, within the IAM services, select Create role. Opt for the Custom trust policy as the trusted entity type.
Here’s the trust policy text that you should use in the custom trust policy text box:
Note: Replace the GitHub
username/repository
with your own.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${account-id}:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:komminarlabs/cd-with-github-actions:*"
}
}
}
]
}
Name the role as GitHubTriggerRole
. After creating the role, attach the following policy as an inline policy to grant access for writing logs to CloudWatch and the necessary permissions to trigger a CodeBuild project.
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:PutLogEvents",
"logs:GetLogEvents",
"logs:CreateLogStream",
"logs:CreateLogGroup"
],
"Effect": "Allow",
"Resource": "arn:aws:logs:eu-central-1:${account-id}:*"
},
{
"Action": [
"codebuild:UpdateReport",
"codebuild:StartBuild",
"codebuild:CreateReportGroup",
"codebuild:CreateReport",
"codebuild:BatchPutTestCases",
"codebuild:BatchPutCodeCoverages",
"codebuild:BatchGetBuilds"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
2. CodeBuild Role:
Next, let’s create a second role for CodeBuild to assume. When you click the Create role button, select CodeBuild as the AWS Service. Attach the existing managed policy AWSCodeBuildAdminAccess
to this role.
CodeBuild
Now, head over to the CodeBuild service and select Create build project. Specify the project name as CD-CodeBuild. In the Source section, choose GitHub from the dropdown and include the Repository URL: https://github.com/komminarlabs/cd-with-github-actions
Note: You have the option to authenticate with GitHub using either OAuth or a personal token.
Apply the following Environment settings, and select the role you previously created.
Select Insert build commands under Buildspec, then paste the following content. Finally, create the CodeBuild project.
version: 0.2
phases:
install:
runtime-versions:
python: 3.11
build:
commands:
- echo "This is a test"
Testing
Development
Alright, now that we’ve configured everything on both the GitHub and AWS sides, it’s time to put our setup to the test. To do this, I used a dummy_file.txt
in the GitHub repository for testing purposes.
After pushing a change directly to the develop
branch, it promptly initiated a deployment to the development environment. The deployment was successful, and you can view the corresponding CodeBuild logs within the Actions.
Production
With the successful deployment to the development
environment, let’s now create a pull request to the main
branch to trigger the production
deployment.
As depicted below, the pull request to main
displays the status of the deployment to development
. You can enforce this deployment check as mandatory in the branch protection rules for added reliability.
Now, proceed to merge this pull request. The deployment to the production
environment will pause and await manual approval, as we’ve included reviewers as a protection rule in the production
environment.
Alright, after a thorough manual review of the changes 😊, let’s proceed with approving this deployment. To do so, click on Review deployments, select the production
environment, and then click on Approve and deploy.
With the approval in place, the production
deployment process is now initiated.
Wrapping it up
I hope this blog has provided you with a clear understanding of how to leverage GitHub environments to enhance your continuous deployment process.