Programmatically Create AWS ECR Repository and Commit Docker Image to ECR

Submitted by nigel on Saturday 7th December 2019

This is a tutorial for using AWS CLI to programmatically create an AWS ECR repository and then commit a Docker image to that repository. This is the first step in a series of tutorials for creating an automated testing framework in AWS Fargate which can be one step of a DevOps pipeline. 

As an example I am going to use the Docker php:7.2-apache image and to make it a little more complex, add a few more PHP extensions using a new Dockerfile so that my new image will be Drupal 8 compliant. 

Let's get started. 

Pull Base Image and Define Dockerfile
The base php:7.2-apache image needs to be pulled from Docker Hub. Issue the following command.
$ docker pull php:7.2-apache
7.2-apache: Pulling from library/php
000eee12ec04: Pull complete 
8ae4f9fcfeea: Pull complete 
60f22fbbd07a: Pull complete 
ccc7a63ad75f: Pull complete 
a2427b8dd6e7: Pull complete 
91cac3b30184: Pull complete 
d6e40015fc10: Pull complete 
240e21c03bb4: Pull complete 
504858e1e4aa: Pull complete 
9a0523b2d73f: Pull complete 
e3acb84829f4: Pull complete 
28a372733f87: Pull complete 
62ee66cdb80a: Pull complete 
c3f368ddc7aa: Pull complete 
Digest: sha256:602bef9acc8d54527ae5b7ee0f16e5cdb6ab81364310f5123b93b95e357f608d
Status: Downloaded newer image for php:7.2-apache
docker.io/library/php:7.2-apache
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
php                 7.2-apache          fec8a1aaffb8        31 hours ago        410MB
lambci/lambda       go1.x               8d962cde7e27        3 months ago        707MB
sls-docker          latest              8d962cde7e27        3 months ago        707MB
Yes the docker image is there. Now I need to create a directory for the Dockerfile and a sample index.php file which will prove our image is working.
$ mkdir automated && cd automated
By default the Docker php image uses /var/www/html as Apache's docroot. However, I'm going to alter this to something more standard for Drupal web apps - a directory called docroot will be created and will contain a simple index.php containing a call to phpinfo();
$ mkdir docroot && echo "<?php phpinfo();" > docroot/index.php
The Dockerfile should now be created and populated. There a number of activities that need to be performed on it.
  • Reference the base image with a FROM instruction
  • Install the libraries that support memcached and graphics manipulation
  • Enable memcached PHP extension
  • Configure the graphics PHP extension
  • Install the graphics PHP extension
  • Copy the files in the current directory and below into the Docker image at the /var/www/html ensuring the permissions are correct for the apache user and group
  • Set the environment variable AH_SITE_ENVIRONMENT to devops - this is an Acquia Cloud environment variable and only of any significance for those using Acquia as their host - or need a setting in my case to denote that devops is not running on Acquia
  • Change the default location for the docroot - that means setting an environment variable and using the sed editor to change the configuration in the sites files
Dockerfile
FROM php:7.2-apache
RUN apt-get update && apt-get install -y libmemcached-dev zlib1g-dev \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libwebp-dev \
    && pecl install memcached \
    && docker-php-ext-enable memcached \
    && docker-php-ext-configure gd --with-gd --with-webp-dir --with-jpeg-dir \
       --with-png-dir --with-zlib-dir --with-freetype-dir \
    && docker-php-ext-install gd
COPY --chown=www-data:www-data ./ /var/www/html/.
ENV AH_SITE_ENVIRONMENT devops
ENV APACHE_DOCUMENT_ROOT /var/www/html/docroot
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
Build the Image, Run the Container
phpinfo
Once the Dockerfile is complete, it is built with
$ docker build -t d8codebase .
and to confirm it's built correctly:
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
d8codebase          latest              c2193f9778a6        33 minutes ago      439MB
<none>              <none>              2a71964685e0        2 hours ago         432MB
php                 7.2-apache          fec8a1aaffb8        35 hours ago        410MB
lambci/lambda       go1.x               8d962cde7e27        3 months ago        707MB
sls-docker          latest              8d962cde7e27        3 months ago        707MB
Great it's there. Now to spin up the container.
$ docker run -d -p 8080:80 d8codebase
Note I am mapping port 8080 to the container's port 80 which probably won't be pertinent to you. Long story I will try to make short. I write my blogs on my Macbook on a local network in the 192.168 range assigned by my ISP's hub. But I develop and test all the practical work in my blogs in a VM in the IP 10. range. Routing between 192.168 and 10. is difficult without port forwarding - so I port forward 127.0.0.1 8080 to my VM's 10.0.2.15 8080, so when I run my Docker Apache container I must remember incoming is on port 8080 and map that against the container's port 80. Hope that makes sense!

Now check it's running:
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
9a6dc2fb88ff        d8codebase          "docker-php-entrypoi…"   44 minutes ago      Up 44 minutes       0.0.0.0:8080->80/tcp   agitated_feistel
Yes, and we can see from the screenshot above that it's picking up the memcached and the gd graphics extensions. Success.
Programmatically Create the AWS ECR Repository
ECR Repository
This is quite easy and I've put it into a shell script that requires one parameter - the repo name to be created. The work is done by the aws ecr create-repository call. The rest of the code is simply checking for error conditions. The screenshot above was taken on the AWS console after the shell script below was successfully run with a parameter of d8codebase.
#!/bin/bash
 
#Programmatically create an ECR repo
 
# Check whether we have the args
if [[ -z $@ ]]; then
	echo "usage: [repo_name]"
	exit 1
fi
 
# Create the repo
REPO_NAME=`aws ecr create-repository --repository-name "$1" | jq .repository.repositoryName -r`
 
# Check it was created ok. We already get a quality stderr msg from aws cli so just quit on error.
# Similarly, if already exists it will tell us
if [[ "${REPO_NAME}" != $1 ]]; then
	exit 2
fi
 
echo "Successfully created ${REPO_NAME}"
Login to Docker and Push Image to ECR
Docker Push
Now the script to automatically login to Docker and push the image to ECR. Note that AWS ECR does require you to use the docker login command, and therefore provides a AWS CLI ECR command get-login which returns a token than can be piped into the docker login command. The token retrieved from ECR has a large payload, and it is top and tailed with other metadata beyond just the password. Therefore that needs to be removed, and if you look closely at the script below, you can see that I pipe the entire returned payload through sed with a regex, and then the result is piped onwards to docker login. Hopefully the rest is quite self explanatory - and you can see the screenshot above that the push has worked and the image can now be included in ECS / Fargate task definitions.
#!/bin/bash
 
#Programmatically push to an ECR repo
 
# Check whether we have the args
if [[ -z $@ ]]; then
	echo "usage: [repo_name]"
	exit 1
fi
 
REPO_NAME=$1
ECR_ACCOUNT="XXXXXXXXXX.dkr.ecr.eu-west-2.amazonaws.com"
ECR_REPO="${ECR_ACCOUNT}/$1"
 
# AWS Authentication - since we need to use docker push
# The sed regex will top and tail the additional meta data sent by the aws login request.
aws ecr get-login --region eu-west-2  | sed -e 's/^.*-p \(.*\)\s\-\e.*$/\1/' |  docker login --password-stdin -u AWS ${ECR_ACCOUNT}
 
# Get the image id of the Docker build. If there is a "latest" use that else get the first without latest
TAG=`docker images | grep -w "${REPO_NAME}" | grep latest | awk '{ print $3; }'`
if [[ -z ${TAG} ]]; then
        TAG=`docker images | grep -w "${REPO_NAME}" | awk '{ print $3; }'`
fi
 
docker tag "$TAG" "${ECR_REPO}"
docker push "${ECR_REPO}"