Hello everyone and welcome to the backend master class.
In this lecture, we will learn how to use Github Action to automatically build and push Docker image to AWS ECR. First, we have to create a repository to store our Docker images.
So let's open the AWS console, and select the region.
I'm in Europe, so I'm gonna choose Ireland ECR, or Amazon Elastic Container Registry is a fully-managed Docker container registry that makes it easy to store, manage and deploy Docker container images.
You can look for it in the search box at the top of the page.
If it's the first time you use ECR, you can click on this Get Started
button to create a new repository.
Or we can expand the left-hand side menu, and choose Repositories
.
Right now, there're no repositories yet. So let's click this button to create a new one.
You can set the visibility of the repository to either Private
or Public
.
If you choose Private
, then no one can push or pull image in this
repository, unless they're given access permissions. Next, we have to fill
in the name of the repository in this box. I'm gonna use simplebank
.
Note that we must follow naming convention. You can also config the Tag immutability
. If enabled then it will prevent image tags from being
overwritten by subsequent image pushes using the same tag. I will leave
it as disabled by default.
Another setting is Scan on push
. If enabled, Amazon ECR will do a security
scan on the image to help identify software vulnerabilities. I will leave
it as disabled for now. The last setting is KMS encryption
, which allows
us to use AWS Key Management Service to encrypt images instead of using
the default encryption setting: AES 256
. For now, let's just use the
default one. So I'm gonna click Create repository
.
And voila, the simplebank
repository is successfully created. Here's the
URI of this repository, which we can use to push and pull images later.
If you're using AWS CLI tool, you can take a look at this page to see commands to push Docker images to this repository.
Basically, you will have to authenticate your Docker client, build your
Docker image, tag your image, and finally push it to the repository.
However, normally we don't push the image directly from our development
machine. Instead, we will use GitHub Actions to automatically build, tag
and push the image for us whenever new codes are merged to the master
branch.
So let's open our simple bank project in Visual Studio Code. First, I'm
gonna rename our existing ci.yml
file to test.yml
and change its
name to "Run unit tests", because that's the main responsibility of
this workflow. Let's copy the content of this workflow.
Then, I'm gonna create a new workflow named deploy.yml
. We will use this
workflow to build Docker image and later deploy it to production. Now
let's paste in the content of the test workflow and change its name to
"Deploy to production". This workflow will only be triggered when there's
a push on master
branch, so I'm gonna remove the pull request event from
the list. Then, in the jobs section, we will declare the first one to
build and push Docker image to Amazon ECR. It will run on ubuntu-latest
as normal.
name: Deploy to production
on:
push:
branches: [ release ]
jobs:
build:
name: Build image
runs-on: ubuntu-latest
Next, we have to write the steps to perform this job. For this, I'm gonna
use some existing GitHub Actions. On github.com,
let's open Marketplace
, select Actions
type and search for AWS ECR
.
There are many results, but this Amazon ECR Login Action
is the official
one, written by AWS.
So let's open it. Here in this page, we can see how to use it.
I'm gonna copy this template
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: my-ecr-repo
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
and paste it to our deploy workflow file. Now, as you can see, the first
step is to login to Amazon ECR. And the amazon-ecr-login
action is
used for this purpose. But in order for it to work, we have to provide
some credentials to access our AWS account. In Credentials and Region
section of the documentation we can see how to do it with another Github
action: configure-aws-credentials
. So let's copy this template
- 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_SECRET_ACCESS_KEY }}
aws-region: us-east-2
and paste it here, as the first step of our workflow. Now we need to provide 2 secrets: AWS access key ID and AWS secret access key. Note that we won't write them directly in this file as plaintext, but they should be encrypted and stored in the GitHub Secrets. Then will be loaded later as environment variables when the workflow is run. We will learn how to do that in a moment.
Now, besides the secret, we must also provide the aws-region
we want
to access. As I'm using AWS Ireland, or eu-west-1
, I'm gonna copy its
name from the URL of this page. And paste it to our workflow here.
- 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_SECRET_ACCESS_KEY }}
aws-region: us-east-1
Alright, now let's create some credentials to allow GitHub to access our AWS account. In the console, I'm gonna search for IAM
Here it is. You can read more about it in this documentation page.
Basically, IAM stands for Identity and Access Management. It is a web
service that helps you securely control access to AWS resources. You
can use it to control who is authenticated and authorized to use your
AWS resources. There are several kinds of identities you can set up, you
can see them in the Identities
section of the left-hand side menu.
The first one is User
, which represents 1 single person or application.
The second one is User Group
, which let you specify the same set of
permissions for multiple users at once.
As you can see in the picture,
you can create groups for different team, with different permissions,
such as: Admins
, Developers
and Testers
.
Then another identity type is IAM roles. It is pretty similar to IAM user, but instead of being uniquely associated with 1 single person or application, a role can be assumable by anyone who needs it. You can learn all bout it by reading the documentation.
For the purpose of this lecture, we're just gonna use IAM user. So
let's go back to the AWS console. Select Users
section on the left
menu. And click Add user
. First, we have to choose a username for it.
Let's say github-ci
. Then in the access type section, we should choose
Programmatic access
, since this is not a human user, but an
application instead. This will enable an access key ID and secret access
key for the AWS API, CLI, SDK and other development tools. Alright, now
let's click Next: Permissions
.
Here,
we will be able to set some permissions for the user we're gonna
create. There are several options: either Add user to group
, Copy permissions from existing user
, or Attach existing policies directly
.
I'm gonna create a new group and add user to it. For the Group name
,
let's call it deployment
. Then in the Filter policies
box, let's
search for Elastic Container Registry.
In the result list, we can see the Full Access
policies, which allows
all administrative access to Amazon ECR resources. The PowerUser
access provides all access except deletion or the ReadOnly
access
that only allows reading data from the ECR repositories.
In our case, we want to push (or write data) to the repo, so we can
choose either Full Access
or Power User
. I'm gonna use Full Access
.
And click Create Group
.
Now you can see the deployment
group is created, and the github-ci
user will be added to this group. Let's click Next: Tags
.
IAM tags are key-value pairs you can add to your user, which can help
you better organize, track or control access for this user. It is
optional, so I'm not gonna add any tags for now. Let's click
Next: Review
.
In this page, we can review all user details before creating it. One
thing we haven't talked about is the permission boundary. If you go
back to the previous step, where we set up user's group and
permissions, you can see the Set permissions boundary
section at the
bottom. Basically, it is a way for us to control the maximum
permissions this user can have.
Note that this setting doesn't grant permissions by itself, but it just
limits the maximum permissions can be granted to the user. It is also
optional, so I'm just gonna create user without a permissions' boundary
for now. Alright, everything looks good, let's click Create user
!
And voilà, the github-ci
user has been successfully created. It is
added to the deployment
group, and the access key for programmatic
access is also available. We can now copy and add it to GitHub
Secrets.
So let's open our Simple bank
GitHub repository. Open the Settings
tab,
and select Secrets
section on the left-hand side menu. GitHub Secrets are
environment variables that are encrypted, and can be used to provide some
sensitive input data to GitHub Actions. There are 2 types of secret. First,
the environment secrets, which will only be available to one specific
environment. It is used when you want to have different secret values for
different environments, such as testing, staging, or production... And the
second type is repository secrets, whose values will be available for the
whole repository. And this will be the one we're gonna use today.
So let's click New repository secret
. For the secret name, let's copy it
from our workflow. The first secret is AWS_ACCESS_KEY_ID
. And we can get
its value from the AWS console. Let's copy, and paste it in this box. Then
click Add secret
.
OK, now let's do the same for the second secret: its name should be
AWS_SECRET_ACCESS_KEY
. And in the AWS console, let's click Show
to see
its value. Copy it, and go back to Github to paste in the value.
Then click Add secret
. Alright, now the AWS access key and secret are
ready.
Let's go back to the GitHub workflow. In the second step, the key will be
used to login to Amazon ECR and its output will be used in the third step.
Here you can see that, the ECR_REGISTRY
variable will take the output
registry
from the previous step.
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: my-ecr-repo
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
We also define another variable for the ECR repository. In our case, it
should be simplebank
, as we're created before. So let's paste in the name
here.
ECR_REPOSITORY: simplebank
The last variable we define here is the IMAGE TAG
. Basically, we will use
the GitHub SHA of the commit to tag the image. It's reasonable, because
each push to master
will have different commit hash, so naturally, we
would want to tag the image with a new version associated with that commit.
Now in the run
section, we will run 2 commands: the first one is docker build
, which will build and tag the image locally. And the second one is
docker push
to push the output image to ECR. All the variables we
declared above are used in these 2 commands.
And I think that will be it! Let's push this new workflow to Github to see how it goes.
In the terminal, let's run
git status
to see the change.
Run
git add .
to stage everything, git status
again to see what's gonna be committed.
Then run git commit
with a message saying "deploy workflow: build and
push Docker image to ECR". Finally, run git push origin master
to push it
to GitHub. Oops, the push is rejected. That's because the master
branch
is protected, so we cannot push changes to it directly, and a pull request
must be created if we want to merge to master
. You can add protection
rules in the Settings
tab, Branches
section.
Here you can see my master
branch protection rule. Basically, I just require
the status check to pass before merging, which means that the unit test
workflow must be successful. You can also require the number of pull-request
reviews approvals if you want.
OK, now because of the protection rule, we have to create a new branch. Let's
call it ft-/ci-build-image
. Then push this branch to Github. Now in the
simplebank
repo, let's open the Pull requests
tab, and click New pull request
. The base branch is master
, and the compare branch should be
ft-/ci-build-image
. We can see the changes here.
It's looking good. So let's click Create pull request
. You can change
name and write some description if you like. Then click Create pull request
one more time.
OK, the PR is successfully created. And the unit test workflow is being
run. While waiting for it to finish, let's take a look at the changes we've
made. So the deploy
workflow will be triggered on a push to master
branch.
The first job is build Docker image. It will configure AWS credentials using
the repository secrets, then login the Amazon ECR, and finally build, tag
and push the image. OK, now let's go back to Conversation
tab. The test
is still running. And you can see here, the Merge pull request
button
is disabled, because it requires the test to pass before merging.
Alright, now the unit tests are completed, all checks have passed. So we can
click Merge pull request
, Confirm merge
and Delete the feature branch.
OK, now if we go the Code
, on the master
branch, we can see the 2 running
CI workflows.
Let's open the deploy
workflow. Oops, it failed.
The steps to configure AWS credentials and login to Amazon ECR are OK. But
there's an error in the build image step. The Dockerfile
is not found.
Oh, that's because I forgot 1 step to checkout the code. So let's open
the test
workflow and copy this step.
- name: Check out code into the Go module directory
uses: actions/checkout@v2
Then paste it to deploy
workflow as the first step.
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
We're not running this workflow on a Golang image, so let's rename it to
just: "Check out code". OK, now I think this will fix the issue. Let's
commit the change: "Deploy workflow: add check out code step". And push
it to GitHub on the ft/ci-build-image
branch. We can open this URL
https://github.com/techschool/simplebank/pull/new/ft/ci-build-image
on the browser to create a new pull request. OK, the PR is created.
Let's wait for the unit tests to finish. Oh, looks like the current branch
is out-of-date with the master
branch.
That's because we've merged the previous PR to master
. But, haven't updated
it locally yet. So in the terminal, let's checkout the master
branch, run
git pull
to fetch and merge new changes to our local master
. Then
git checkout
the feature CI build image branch. Now we can run
git merge master
to merge the master
branch into current branch.
Everything is up to date. So let's push it to GitHub again. Now we have to
wait for the unit test. Let's look at the Files changed
tab. We only
add 1 step to check out code, so it's looking good.
Alright, now all checks have passed. Let's merge the pull request to
master
. Here instead of a normal Merge we can also use Squash and merge to
combine all commits into 1 single commit on master
branch. It will help
keep the commit history on master
clean and tidy.
OK, now let's check the list of commits on master
branch.
On the last commit, we can see the CI workflows are running. Let's open
the deploy
workflow. It is building image.
All the previous steps were successful. We can expand this step to see the details. OK, everything is completed now. All steps are successful.
The image is built and pushed to Amazon ECR. So let's open AWS console to check it!
Here, in the simplebank
repository we can see 1 new image with the
latest commit hash tag was just pushed. And its size is 26.17 MB. Awesome!
So that's how we setup Github Actions to build and push image to ECR.
Before we finish, I'm gonna show you how to check your billing. It is
very important to control how much you spend on your AWS services. Just
click on your account name on the top right corner, and select My Billing Dashboard
.
Here you can see your spend summary.
Scroll down a bit, we will see the top free tier services by usage. Right now, we're only using Amazon ECR with 500 MB free storage per month. And up to now, we're just using about 1% of it.
But why there's also Amazon Simple Storage Service here? Well that's because ECR uses S3 to store the Docker images. And we're given 2000 free requests of Put, Copy, Post or List. So if we go beyond these numbers, we will be charged some money. If you open the ECR documentation page, and read about its pricing, you can see that, the 500 MB free storage per month constraint is only applied to private repository. Besides this, ECR also offers 50 GB always-free storage if you use it for your public repository. So pretty cool, isn't it?
And with this, I'm gonna wrap this lecture about building and pushing Docker images to Amazon ECR using GitHub Actions. I hope you find it useful.
Thanks a lot for watching, happy learning, and see you in the next lecture!