# Pushing Docker Images to AWS ECR

I’ve been using the image registry on GitLab for quite a while now and loved the convenience of having my images living in the same place as my code. However, recently GitLab introduced a soft limit on transfers and that’s cramping my style. I’m moving a lot of my images onto Amazon Elastic Container Registry (ECR). In this post I look at how to get this set up.

## Setup Registry on ECR

The first thing that we’ll need to do is get a repository set up on ECR.

1. Go to the ECR dashboard.
2. Press .
3. Choose a name for the repository and press . For the purpose of this post we’ll assume that the repository name is “crawler”.
4. Select the newly created repository and press . This will give you all of the information that you need for login, tagging and pushing. Take a screenshot of this for future reference.

## AWS Permissions

You can use existing AWS credentials. If you don’t have a suitable AWS user for pushing an image to ECR then you’ll need to create one. The quick and easy approach to this is to create an IAM user with programmatic access (I name it “registry”) and AmazonEC2ContainerRegistryFullAccess permissions. These permissions are probably too permissive, but they will work for now.

Add the access key and secret access key to your ~/.aws/credentials file. Let’s call that profile entry “registry” too (although you can choose another, more appropriate name!).

[registry]
aws_access_key_id = AKIAZUYM3M4576QJWSX6
aws_secret_access_key = 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


We can immediately test access to the repository by requesting login credentials using the aws CLI. First we’ll set up a couple of environment variables to store the profile name, region and AWS account ID.

AWS_PROFILE=registry
AWS_REGION=us-east-1
AWS_ACCOUNT_ID=999999999999


Now request the credentials.

aws ecr get-login-password --region $AWS_REGION --profile$AWS_PROFILE


If all goes well then the response will be a Base64 encoded string. You can decode that with base64 -d and then pipe into jq. The result should be something like this (the values for the payload and datakey fields have been redacted):

{
"datakey": "...",
"version": "2",
"type": "DATA_KEY",
"expiration": 1645469652
}


The expiration value indicates (use https://www.epochconverter.com/ to convert to date and time) that the credentials are valid for 12 hours.

## Pushing by Hand

Let’s start by taking a look at how to manually push an image to ECR. First we’ll need to connect our local Docker to the repository.

# Store credentials in environment variable.
ECR_LOGIN=$(aws ecr get-login-password --region$AWS_REGION --profile $AWS_PROFILE) # Docker login to repository. docker login --username AWS --password$ECR_LOGIN $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com


Create a suitable tag.

# Store image name.
IMAGE=crawler
# Create tag (which bundles image name and account ID).
TAG_LATEST=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE:latest  Let’s see what that looks like. echo$TAG_LATEST

999999999999.dkr.ecr.us-east-1.amazonaws.com/crawler:latest


It’s a bit clunky, but that’s because it includes not only the name for the image but also the AWS account ID and region.

Now all we need to do is push.

docker push $(TAG_LATEST)  The build target will simply update the local image. The login target probably never needs to be run explicitly. However, the push target will run it implicitly. It’ll also run the build target and then push the resulting image onto ECR. Once the Makefile is in place it’s a simple process to build and push the image. make build # Not strictly necessary (implicit in "push" target). make push  ## GitLab Okay, so the Makefile significantly reduced the amount of work required to get the image onto ECR. But ideally I don’t even want to have to even have to run make. What if the image could be built and transferred to ECR each time I pushed my work to the remote Git repository? Let’s enlist GitLab CI to do precisely that. There’s a little configuration work required on GitLab to get this working. I set up the following CI environment variables: • AWS_ACCOUNT_ID • AWS_REGION • AWS_ACCESS_KEY_ID and • AWS_SECRET_ACCESS_KEY. The access key and secret access key are those for the IAM user created earlier. The CI pipeline is configured via the .gitlab-ci.yml file. stages: - build variables: IMAGE_NAME: crawler TAG_LATEST:$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest
TAG_COMMIT: $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$CI_COMMIT_SHORT_SHA
DOCKER_TLS_CERTDIR: ""

docker:
image: docker:stable
stage: build
only:
- master
services:
- docker:dind
before_script:
- apk add --no-cache python3 py3-pip
- pip3 install --no-cache-dir awscli
script:
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com - docker pull$TAG_LATEST || true
- docker build --cache-from $TAG_LATEST -t$TAG_COMMIT -t $TAG_LATEST . - docker push$TAG_COMMIT
- docker push \$TAG_LATEST


The variables are similar to the ones that we created in the Makefile. I’ve added in another tag now which is linked to the commit SHA. This will mean that, in addition to the latest image being available on ECR, I’ll also have a history of all previous images. The meat of the configuration lies in the script section of the image job. Let’s step through each command:

1. Login to the Docker registry on ECR. Unlike the corresponding command in the Makefile we’re not using the --profile option here because the corresponding information is picked up from the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.
2. Pull the most recent version of the image. This will speed up the build process because it will act as a cache and prevent unchanged layers from being rebuilt.
3. Build the image (using the most recent version of the image as a cache). Add two tags.
4. Push using the commit tag.
5. Push using the latest tag.

## Conclusion

I’ll be using this as a reference whenever I forget one of the stages in getting this all set up. I hope that it’s useful to you too.