What is GitHub Actions?
GitHub Actions is a feature offered by GitHub that allows you to script out additional automation on top of your existing repositories. This automation is entirely customizable and can automate anything from deployments to your infrastructure (provisioning resources like S3 buckets, EC2 instances, etc.) to automating the compiling of your software solutions.
With GitHub Actions you can choose to run your automation when a specific branch is updated, if anything within your repository is updated, or manually. These workflows also allow you to make decisions during the automation process such as approving an update to your environment or deciding not to follow through with a deployment. GitHub actions is an extremely versatile solution with an infinite amount of applications (including multiple ways to get the same result).
How We’ll be Using GitHub Actions in this Post
The following guide will allow you to trigger automation when an update happens to a specific branch (i.e. your dev branch) that automatically builds a docker image, pushes that image to AWS’s Elastic Container Registry, and refreshes your existing containers to use your latest code updates.
Requirements:
- An AWS account
- Application source code stored in GitHub
Setting Up Your Environment for GitHub Actions
A general best practice is to have three separate environments with separate code sources (either repositories or branches) and integrate some form of automated deployment to ensure you’re not enabling the possibility of human error. The go-to method is to create a repository within GitHub a set up the following:
- Create 3 separate branches: dev, staging, and production
- Ensure your branches are all sync’d
- Enable branch protection and configure it to deny any direct pushes to elevated environments
- Gather credentials for an IAM user located within your AWS account and ensure the user has the least amount of privileges needed to perform the automation
Create the AWS ECR Repositories
Create private repositories in AWS Elastic Container Registry service. These are repositories that are going to store the images your GitHub Actions pipeline you will be building. You will want to have either some form of versioning or separate ECR repositories for specific environments.
Create an IAM User
An IAM user will be used to authenticate with your AWS account from GitHub in order to push to your ECR repositories. Be sure to give it the permissions necessary, but always ensure you’re only enabling permissions that are needed for your specific deployment actions:
AWS Managed Permission: EC2InstanceProfileForImageBuilderECRContainerBuilds
Custom Policy Example:
This example is a bit permissive as it isn’t specifying resources for the ECS related permissions, so your permissions may look a little different.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ECS-Permissions", "Effect": "Allow", "Action": [ "ecs:DeregisterTaskDefinition", "ecs:RegisterTaskDefinition", "ecs:ListTaskDefinitions", "ecs:DescribeTaskDefinition", "ecs:DescribeServices", "ecs:UpdateService" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "iam:PassRole" ], "Resource": "arn:aws:iam::<account-id>:role/ecsTaskExecutionRole" } ] }
Add AWS Access Key ID and Access Key Secret to GitHub Secrets
Use the access keys that were generated when you created your AWS IAM user or generate new ones and navigate to GitHub > your repository > Settings > Secrets > Actions > New repository secret. Add your access key ID and access key secret as two separate secrets. You can also do this at the organization level if you’re performing this for an organization that will need to use these secrets in other repositories.
Set Up GitHub Actions
GitHub actions are stored in your workflows directory and are written via a YAML file, below is a basic configuration that will authenticate with AWS, build your image, tag the image, and push to the ECR repository you created earlier (be sure to update values within the <> characters). Please note, this configuration isn’t utilizing any versioning (i.e. using the commit hash for image tagging) and will overwrite the “latest” image:
name: Docker Image CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_ACCESS_KEY_SECRET }}
aws-region: <region (example: us-east-1)>
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
run: |
docker build -t <account-id>.dkr.ecr.us-east-1.amazonaws.com/<repo-name>:latest .
docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/<repo-name>:latest
echo "::set-output name=image::<account-id>.dkr.ecr.us-east-1.amazonaws.com/<repo-name>:latest"
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition definition_name \
--query taskDefinition > task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: <container-name>
image: ${{ steps.build-image.outputs.image }}
- name: Deploy Amazon ECS task definition to Dev
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: <service-name>
cluster: <cluster-name>
wait-for-service-stability: true
If you plan on having separate pipelines, create additional workflow files and update the following lines:
on:
push:
branches: [ main ] # Change this to dev or your branch name
Test Your Deployment Pipeline
Next, push an update to the branch you enabled actions for and validate the ECR repository image was updated. This process should take a few minutes depending on the size of your build and how long it takes for your service to refresh. All errors should be debuggable from the output in the GitHub actions runs under the Actions tab of your repository.
Looking to do more with Docker? Check out this article on How to Dockerize a PHP Application!