Build and Push Docker image to Amazon ECR

Background

In this post I am going to share a bash script that I use to build a docker container image from a Dockerfile and then upload the built image to Amazon Elastic Container Registry.

Getting a container image up to ECR is an important first step before you can use that image in ECS/EKS or even on a stand alone EC2 server with docker or docker-compose.

The script I share here is a stripped down version of what I use. Readers should develop the script further to suit their needs, add error handling and error notificaiton.

If you follow the script, you can create an Elastic Container Registry, build a docker container and push it upto ECR.

Note

At the end, please remember to delete resources you create. Depending on your usage, you could incur charges on your account.

Requirements

To follow along you will need a few tools and utilities. In my case, I used my Ubuntu Desktop to prepare the demonstration.

  • A Linux environment.
  • In my environment I have already configured an AWS profile called ‘automation’. You can either create a profile manually or run AWS configure to set your access information.
  • I set these environment variables to use my AWS credentials
    • export AWS_DEFAULT_PROFILE=automation
    • export AWS_DEFAULT_REGION=us-east-1
  • Install AWS CLI Version 2 in your environment. I am using the newer syntax for ECR login as specified here.
  • jq to help parse some output from the AWS CLI.
  • Docker Community Edition.

The versions are listed below.

I am running docker as non root user and if you need help setting that up, follow instructions from here.


docker --version
Docker version 19.03.8, build afacb8b7f0
Docker version 20.10.14, build a224086

aws --version
aws-cli/2.2.46 Python/3.8.8 Linux/5.13.0-40-generic exe/x86_64.ubuntu.20 prompt/off

jq --version
jq-1.6

Script

The sample code consists of three files.

  • A bash script which is used to put all things together.
    • This script will create an ECR for you if it does not exist.
    • Set a lifecycle policy for your images.
    • Build a docker image
    • Tag the image
    • Push it up to Amazon ECR.
  • A life cycle policy to manage your untagged images.
  • A Dockerfile to build an image locally.

Let us do a quick review of the files.

The Dockerfile used is a typical example for a base nginx. I used it for the demo purpose, you will probably have something more appropriate for your application.

The life cycle policy is attached to ECR repository to help manage the number of version to keep. As you build newer versions and they change, the ECR repository will end up with untagged version that are no longer being referenced.

The policy ensures that you do not have more than 4 images without a Tag.

Consider this example:

  • You build an image and push it up to ECR as mynginx:latest.
  • You make a change to the Dockerfile and push a new mynginx:latest
  • Now you will have one image which will be un-tagged.
  • Assume a newer nginx image is pulled and you rebuild and push to Amazon ECR.
  • Now you will have two images which will be un-tagged.
  • Over time, as newer versions are built and pushed, you will end up with images that are not being referenced, but still occupy space.
  • The life cycle policy ensure that you do not end up with a large number of un-tagged images. It helps keep your cost down.

The bash script is designed to be run from any Linux environment and can be used to push images to any AWS Region. For this reason, I have not set region information in the script.

The script expects AWS Region, the name of the repository to create and the tag to be used to identify the docker image.

The script expects the Dockerfile to be in a folder <base folder>/build-images/<repo-name>/Dockerfile. <base folder> is where the bash script and life cycle policy reside.

Line # 14 through 23 checks if the repository already exists. If it doesn’t then a repository is created and the life cycle policy is attached to it.

Line # 32 extracts the registry information from the repository. This is used later to login to Amazon ECR.

Line # 35 shows the login command to Amazon ECR. This will give us access to push an image up to the ECR.

Rest of the script deals with building the docker image, tagging it and pushing it up to Amazon ECR.

Testing

The following shows the command I used to create mynginx image and tag it as latest, to be pushed up to the repository in us-east-1 region.

If you are using any other region, you can substitute the correct region and run it accordingly.

If you plan on using this image in more than one region it is better to run the command for each of the regions, so the image is available in the region where you need it.

find .
.
./ecr-lifecycle-policy.json
./build-images
./build-images/mynginx
./build-images/mynginx/Dockerfile
./ecr-repo.sh

./ecr-repo.sh us-east-1 mynginx latest
{
    "registryId": "XXXXXXXXXXX",
    "repositoryName": "mynginx",
    "lifecyclePolicyText": "{\"rules\":[{\"rulePriority\":1,\"description\":\"Expire untagged and count more than 4\",\"selection\":{\"tagStatus\":\"untagged\",\"countType\":\"imageCountMoreThan\",\"countNumber\":4},\"action\":{\"type\":\"expire\"}}]}"
}
XXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/mynginx
XXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/sbali/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM nginx:latest
 ---> 9beeba249f3e
Step 2/3 : EXPOSE 80
 ---> Using cache
 ---> 96a66c2ff55b
Step 3/3 : CMD ["nginx", "-g", "daemon off;"]
 ---> Using cache
 ---> 03027a2872bf
Successfully built 03027a2872bf
Successfully tagged mynginx:latest
The push refers to repository [XXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/mynginx]
6c7de695ede3: Pushed
2f4accd375d9: Pushed
ffc9b21953f4: Pushed
latest: digest: sha256:3cade80a3f755517dfe6ad6d41b417fda900db0a3422ec73ebb3618a0958418d size: 948

As you can see from the output above, the script created a new repository in Amazon ECR, built a docker image with identifier mynginx:latest and then pushed it to the repository available in your account.

If you navigate to the AWS console in a browser, and look at ECR Repository, you should see the image you just created.

Next, I deleted all the local images on my desktop and did a fresh login to the ECR registry and ran the command as shown below to see if I can pull the image from ECR and run it.

Note: I deleted the local images using docker rmi and did a fresh login to AWS ECR before trying to run a container.


docker rmi <local image>

export AWS_DEFAULT_REGION=us-east-1
export AWS_DEFAULT_PROFILE=automation
aws ecr get-login-password | docker login --username AWS --password-stdin xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com

docker run  --name mynginx --rm  -p 8080:80 -d XXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/mynginx:latest
Unable to find image 'XXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/mynginx:latest' locally
latest: Pulling from mynginx
afb6ec6fdc1c: Pull complete
b90c53a0b692: Pull complete
11fa52a0fdc0: Pull complete
Digest: sha256:3cade80a3f755517dfe6ad6d41b417fda900db0a3422ecxxxxxxxxxxxxxxxxxxx
Status: Downloaded newer image for XXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/mynginx:latest
0dc646973c20451abb3371227cc1370810d72d74860xxxxxxxxxxxxxxxxxxx

docker ps
CONTAINER ID        IMAGE                                                         COMMAND                  CREATED             STATUS              PORTS                  NAMES
0dc646973c20        XXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/mynginx:latest   "nginx -g 'daemon of…"   14 seconds ago      Up 12 seconds       0.0.0.0:8080->80/tcp   mynginx

The captured output above shows, no image was found locally and it was pulled down from ECR and used to start a Nginx container.

Summary

This demonstrates, how easy it is to setup a build and push image to ECR using simple AWS CLI and shell script.

You can easily adapt and modify the sample script to handle multiple regions and even run it in a Jenkins pipeline.

In fact, I have a Jenkins project for all of my images that use a similar script to continuously build and upload images to Amazon ECR.

Please remember to remove any resources you created to avoid incurring charges in your account.

Let me know if you have any questions or need help with this by commenting below.

Further Reading

Photo Credit

Thanks to frank mckenna for sharing their work on Unsplash.

2 COMMENTS

    • Hello, thanks for trying out the example.
      I just re-tested it and found no error. It may be possible that you have a copy and paste error in your local script.
      For ref, I have also added the output of find to show the layout of the file system.

Leave a Reply