Recently, I was asked a question regarding sharing Docker images from one AWS Account’s Amazon Elastic Container Registry (ECR) with another AWS Account who was deploying to Amazon Elastic Container Service (ECS) with AWS Fargate. The answer was relatively straightforward, use ECR Repository Policies to allow cross-account access to pull images. However, the devil is always in the implementation details. Constructing ECR Repository Policies can depend on your particular architecture, choice deployment tools, and method of account access. In this brief post, we will explore a common architectural scenario that requires configuring ECR Repository Policies to support sharing images across AWS Accounts.

Introduction

There are two scenarios I frequently encounter, which require sharing ECR-based Docker images across multiple AWS Accounts. In the first scenario, a vendor wants to securely share a Docker Image with their customer. Many popular container security and observability solutions function in this manner.

Below, we see an example where an application platform consists of three containers. Two of the container’s images originated from the customer’s own ECR repositories (right side). The third container’s image originated from their vendor’s ECR registry (left side).

In the second scenario, an enterprise operates multiple AWS Accounts to create logical security boundaries between environments and responsibilities. The first AWS Account contains the enterprise’s deployable binary assets, including ECR image repositories. The enterprise has additional accounts, one for each application environment, such as Dev, Test, Staging, and Production. The ECR images in the repository account need to be accessed from each of the environment accounts, often across multiple AWS Regions.

Below, we see an example where the deployed application platform consists of three containers, of which all images originated from the ECR repositories (left side). The images are pulled into the Production account for deployment to ECS (right side).

In this post, we will explore the first scenario, a vendor wants to securely share a Docker Image with their customer. We will demonstrate how to share images across AWS Accounts for use with Docker Swarm and ECS with Fargate, using ECR Repository Policies. To accomplish this scenario, we will use an existing application I have created, a RESTful, HTTP-based NLP (Natural Language Processing) API, consisting of three Golang microservices. The edge service, nlp-client , communicates with the rake-app service and the prose-app service.

The scenario in the demonstration is that the customer has developed the nlp-client and prose-app container-based services, as part of their NLP application. Instead of developing their own implementation of the RAKE (Rapid Automatic Keyword Extraction) algorithm, they have licensed a version from a vendor, the rake-app service, in the form of a Docker Image.

The NPL API exposes several endpoints, accessible through the nlp-client service. The endpoints perform common NLP operations on text, such as extracting keywords, tokens, and entities. All the endpoints are visible by hitting the /routes endpoint.

[

{

"method": "POST",

"path": "/tokens",

"name": "main.getTokens"

},

{

"method": "POST",

"path": "/entities",

"name": "main.getEntities"

},

{

"method": "GET",

"path": "/health",

"name": "main.getHealth"

},

{

"method": "GET",

"path": "/routes",

"name": "main.getRoutes"

},

{

"method": "POST",

"path": "/keywords",

"name": "main.getKeywords"

}

]

Requirements

To follow along with the demonstration, you will need two AWS Accounts, one representing the vendor and one representing one of their customers. It’s relatively simple to create additional AWS Accounts, all you need is a unique email address (easy with Gmail) and a credit card. Using AWS Organizations can make the task of creating and managing multiple accounts even easier.

I have purposefully used different AWS Regions within each account to demonstrate how you can share ECR images across both AWS Accounts and Regions. You will need a recent version of the AWS CLI and Docker. Lastly, you will need sufficient access to each AWS Account to create resources.

Source Code

The demonstration’s source code is contained in four public GitHub repositories. The first repository contains all the CloudFormation templates and the Docker Compose Stack file, as shown below.

.

├── LICENSE

├── README.md

├── cfn-templates

│ ├── developer-user-group.yml

│ ├── ecr-repo-not-shared.yml

│ ├── ecr-repo-shared.yml

│ ├── public-subnet-public-loadbalancer.yml

│ └── public-vpc.yml

└── docker

└── stack.yml

Each of the other three GitHub repositories contains a single Go-based microservice, which together comprises the NLP application. Each respository also contains a Dockerfile.

.

├── Dockerfile

├── LICENSE

├── README.md

├── buildspec.yml

└── main.go

The commands required to clone the four repositories are as follows.

git clone --branch master \

--single-branch --depth 1 --no-tags \

https://github.com/garystafford/ecr-cross-account-demo.git



git clone --branch master \

--single-branch --depth 1 --no-tags \

https://github.com/garystafford/nlp-client.git



git clone --branch master \

--single-branch --depth 1 --no-tags \

https://github.com/garystafford/prose-app.git



git clone --branch master \

--single-branch --depth 1 --no-tags \

https://github.com/garystafford/rake-app.git

We will use AWS CloudFormation to create the necessary resources within both AWS Accounts. In the customer account, we will also use CloudFormation to create an ECS Cluster and an Amazon ECS Task Definition. The Task Definition defines how ECS will deploy our application, consisting of three Docker containers, using AWS Fargate. In addition to ECS, we will create an Amazon Virtual Private Cloud (VPC) to house the ECS cluster and a public-facing, Elastic Load Balancing (ELB) Network Load Balancer (NLB) to load-balance our ECS-based application.

Throughout the post, I will use AWS Cloud9, the cloud-based integrated development environment (IDE), to execute all CloudFormation templates using the AWS CLI. I will also use Cloud9 to build and push the Docker images to the ECR repositories. Personally, I find Cloud9 easier to switch between multiple AWS Accounts and AWS Identity and Access Management (IAM) Users, using separate instances of Cloud9, verses using my local workstation. Conveniently, Cloud9 comes preinstalled with many of the tools you will need for this demonstration.

Creating ECR Repositories

In the first AWS Account, representing the vendor, we will execute two CloudFormation templates. The first template, developer-user-group.yml, creates the Development IAM Group and User. The Developer-01 IAM User will be given explicit access to the vendor’s rake-app ECR repository. I suggest you change the DevUserPassword parameter’s value to something more secure.

# change me

IAM_USER_PSWD=T0pS3cr3Tpa55w0rD



aws cloudformation create-stack \

--stack-name developer-user-group \

--template-body file://developer-user-group.yml \

--parameters \

ParameterKey=DevUserPassword,ParameterValue=${IAM_USER_PSWD} \

--capabilities CAPABILITY_NAMED_IAM

Below, we see an example of the resulting CloudFormation Stack showing the new Development IAM User and Group.

Next, we will execute the second CloudFormation template, ecr-repo-shared.yml, which creates the vendor’s rake-app ECR image repository. The rake-app repository will house a copy of the vendor’s rake-app Docker Image. But first, let’s look at the CloudFormation template used to create the repository, specifically the RepositoryPolicyText section. Here we define two repository policies:

The AllowPushPull policy explicitly allows the Developer-01 IAM User to push and pull versions of the image to the ECR repository. We import the exported Amazon Resource Name (ARN) of the Developer-01 IAM User from the previous CloudFormation Stack Outputs. We have also allowed the AWS CodeBuild service access to the ECR repository. This is known as a Service-Linked Role. We will not use CodeBuild in this brief post.

policy explicitly allows the Developer-01 IAM User to push and pull versions of the image to the ECR repository. We import the exported Amazon Resource Name (ARN) of the Developer-01 IAM User from the previous CloudFormation Stack Outputs. We have also allowed the AWS CodeBuild service access to the ECR repository. This is known as a Service-Linked Role. We will not use CodeBuild in this brief post. The AllowPull policy allows anyone in the customer’s AWS Account ( root ) to pull any version of the image. They cannot push, only pull. Of course, cross-account access can be restricted to a finer-grained set of the specific customer’s IAM Entities and source IP addresses.

Note the "ecr:GetAuthorizationToken" policy Action. Later, when the customer needs to pull this vendor’s image, this Action will allow the customer’s User to log into the vendor’s ECR repository and receive an Authorization Token. The customer retrieves a token that is valid for a specified container registry for 12 hours.

RepositoryPolicyText:

Version: '2012-10-17'

Statement:

- Sid: AllowPushPull

Effect: Allow

Principal:

Service: codebuild.amazonaws.com

AWS:

Fn::ImportValue:

!Join [':', [!Ref 'StackName', 'DevUserArn']]

Action:

- 'ecr:BatchCheckLayerAvailability'

- 'ecr:BatchGetImage'

- 'ecr:CompleteLayerUpload'

- 'ecr:DescribeImages'

- 'ecr:DescribeRepositories'

- 'ecr:GetDownloadUrlForLayer'

- 'ecr:GetRepositoryPolicy'

- 'ecr:InitiateLayerUpload'

- 'ecr:ListImages'

- 'ecr:PutImage'

- 'ecr:UploadLayerPart'

- Sid: AllowPull

Effect: Allow

Principal:

AWS: !Join [':', ['arn:aws:iam:', !Ref 'CustomerAccount', 'root']]

Action:

- 'ecr:GetAuthorizationToken'

- 'ecr:BatchCheckLayerAvailability'

- 'ecr:GetDownloadUrlForLayer'

- 'ecr:BatchGetImage'

- 'ecr:DescribeRepositories' # optional permission

- 'ecr:DescribeImages' # optional permission

Before executing the following command to deploy the CloudFormation Stack, ecr-repo-shared.yml, replace the CustomerAccount value, shown below, with your pseudo customer’s AWS Account ID.

# change me

CUSTOMER_ACCOUNT=999888777666 REPO_NAME=rake-app



aws cloudformation create-stack \

--stack-name ecr-repo-${REPO_NAME} \

--template-body file://ecr-repo-shared.yml \

--parameters \

ParameterKey=CustomerAccount,ParameterValue=${CUSTOMER_ACCOUNT} \

ParameterKey=RepoName,ParameterValue=${REPO_NAME} \

--capabilities CAPABILITY_NAMED_IAM

Below, we see an example of the resulting CloudFormation Stack showing the new ECR repository.

Below, we see the ECR repository policies applied correctly in the Permissions tab of the rake-app repository. The first policy covers both the Developer-01 IAM User, referred to as an IAM Entity, as well as AWS CodeBuild, referred to as a Service Principal.

The second policy covers the customer’s AWS Account ID.

Repeat this process in the customer AWS Account. First, the CloudFormation template, developer-user-group.yml, containing Development IAM Group and Developer-01 User.

# change me

IAM_USER_PSWD=T0pS3cr3Tpa55w0rD



aws cloudformation create-stack \

--stack-name development-user-group \

--template-body file://development-user-group.yml \

--parameters \

ParameterKey=DevUserPassword,ParameterValue=${IAM_USER_PSWD} \

--capabilities CAPABILITY_NAMED_IAM

Next, we will execute the second CloudFormation template, ecr-repo-not-shared.yml, twice, once for each of the customer’s two ECR repositories, nlp-client and prose-app . First, let’s look at the template, specifically the RepositoryPolicyText section. In this CloudFormation template, we only define a single policy. Identical to the vendor’s policy, the AllowPushPull policy explicitly allows the previously-created Developer-01 IAM User to push and pull versions of the image to the ECR repository. There is no cross-account access required to the customer’s two ECR repositories.

RepositoryPolicyText:

Version: '2012-10-17'

Statement:

- Sid: AllowPushPull

Effect: Allow

Principal:

Service: codebuild.amazonaws.com

AWS:

Fn::ImportValue:

!Join [':', [!Ref 'StackName', 'DevUserArn']]

Action:

- 'ecr:BatchCheckLayerAvailability'

- 'ecr:BatchGetImage'

- 'ecr:CompleteLayerUpload'

- 'ecr:DescribeImages'

- 'ecr:DescribeRepositories'

- 'ecr:GetDownloadUrlForLayer'

- 'ecr:GetRepositoryPolicy'

- 'ecr:InitiateLayerUpload'

- 'ecr:ListImages'

- 'ecr:PutImage'

- 'ecr:UploadLayerPart'

Execute the following commands to create the two CloudFormation Stacks. The Stacks use the same template with a different Stack name and RepoName parameter values.

# nlp-client

REPO_NAME=nlp-client

aws cloudformation create-stack \

--stack-name ecr-repo-${REPO_NAME} \

--template-body file://ecr-repo-not-shared.yml \

--parameters \

ParameterKey=RepoName,ParameterValue=${REPO_NAME} \

--capabilities CAPABILITY_NAMED_IAM



# prose-app

REPO_NAME=nlp-client

aws cloudformation create-stack \

--stack-name ecr-repo-${REPO_NAME} \

--template-body file://ecr-repo-not-shared.yml \

--parameters \

ParameterKey=RepoName,ParameterValue=${REPO_NAME} \

--capabilities CAPABILITY_NAMED_IAM

Below, we see an example of the resulting two ECR repositories.

At this point, we have our three ECR repositories across the two AWS Accounts, with the proper ECR Repository Policies applied to each.

Building and Pushing Images to ECR

Next, we will build and push the three NLP application images to their corresponding ECR repositories. To confirm the ECR policies are working correctly, log in as the Developer-01 IAM User to perform the following actions.

Logged in as the vendor’s Developer-01 IAM User, build and push the Docker image to the rake-app repository. The Dockerfile and Go source code is located in each GitHub repository. With Go and Docker multi-stage builds, we will make super small Docker images, based on Scratch, with just the compiled Go executable binary. At less than 10-20 MBs in size, pushing and pulling these Docker images, even across accounts, is very fast. Make sure you substitute the variable values below with your pseudo vendor’s AWS Account and Region. I am using the acroymn, ISV (Independent Software Vendor) for the vendor, throughout the code.

# change me

ISV_ACCOUNT=111222333444

ISV_ECR_REGION=us-east-2



$(aws ecr get-login --no-include-email --region ${ISV_ECR_REGION})

docker build -t ${ISV_ACCOUNT}.dkr.ecr.${ISV_ECR_REGION}.amazonaws.com/rake-app:1.0.0 . --no-cache

docker push ${ISV_ACCOUNT}.dkr.ecr.${ISV_ECR_REGION}.amazonaws.com/rake-app:1.0.0

Below, we see the output from the vendor’s Developer-01 IAM User logging into the rake-app repository.

Then, we see the results of the vendor’s Development IAM User building and pushing the Docker Image to the rake-app repository.

Next, logged in as the customer’s Developer-01 IAM User, build and push the Docker images to the ECR nlp-client and prose-app repositories. Again, make sure you substitute the variable values below with your pseudo customer’s AWS Account and preferred Region.

# change me

CUSTOMER_ACCOUNT=999888777666

CUSTOMER_ECR_REGION=us-west-2



$(aws ecr get-login --no-include-email --region ${CUSTOMER_ECR_REGION})

docker build -t ${CUSTOMER_ACCOUNT}.dkr.ecr.${CUSTOMER_ECR_REGION}.amazonaws.com/nlp-client:1.0.0 . --no-cache

docker push ${CUSTOMER_ACCOUNT}.dkr.ecr.${CUSTOMER_ECR_REGION}.amazonaws.com/nlp-client:1.0.0



docker build -t ${CUSTOMER_ACCOUNT}.dkr.ecr.${CUSTOMER_ECR_REGION}.amazonaws.com/prose-app:1.0.0 . --no-cache

docker push ${CUSTOMER_ACCOUNT}.dkr.ecr.${CUSTOMER_ECR_REGION}.amazonaws.com/prose-app:1.0.0

At this point, each of the three ECR repositories has a Docker Image pushed to them.

Deploying Locally to Docker Swarm

As a simple demonstration of cross-account ECS access, we will start with Docker Swarm. Logged in as the customer’s Developer-01 IAM User and using the Docker Swarm Stack file included in the project, we can create and run a local copy of our NLP application in our customer’s account. First, we need to log into the vendor’s ECR repository in order to pull the image from the vendor’s ECR registry.

# change me

ISV_ACCOUNT=111222333444

ISV_ECR_REGION=us-east-2



aws ecr get-login \

--registry-ids ${ISV_ACCOUNT} \

--region ${ISV_ECR_REGION} \

--no-include-email

The aws ecr get-login command simplifies the login process by returning a (very lengthy) docker login command in response ( shown abridged below). According to AWS, the authorizationToken returned for each registry specified is a base64 encoded string that can be decoded and used in a docker login command to authenticate to an ECR registry.

docker login -u AWS -p eyJwYXlsb2FkI...joidENXMWg1WW0 \

https://111222333444.dkr.ecr.us-east-2.amazonaws.com

Copy, paste and execute the entire docker login command back into your terminal. Below, we see an example of the expected terminal output from logging into the vendor’s ECR repository.

Once successfully logged in to the vendor’s ECR repository, we will pull the image. Using the docker describe-repositories and docker describe-images , we can list cross-account repositories and images your IAM User has access to if you are unsure.

aws ecr describe-repositories \

--registry-id ${ISV_ACCOUNT} \

--region ${ISV_ECR_REGION} \

--repository-name rake-app



aws ecr describe-images \

--registry-id ${ISV_ACCOUNT} \

--region ${ISV_ECR_REGION} \

--repository-name rake-app



docker pull ${ISV_ACCOUNT}.dkr.ecr.${ISV_ECR_REGION}.amazonaws.com/rake-app:1.0.0

Running the following command, you should see each of our three application Docker Images.

docker image ls --filter=reference='*amazonaws.com/*'

Below, we see an example of the expected terminal output from pulling the image and listing the images.

Build the Docker Stack Locally

Next, build the Docker Swarm Stack. The Docker Compose file, stack.yml, is shown below. Note the location of the Images.

version: '3.7'



services:

nlp-client:

image: ${CUSTOMER_ACCOUNT}.dkr.ecr.${CUSTOMER_ECR_REGION}.amazonaws.com/nlp-client:1.0.0

networks:

- nlp-demo

ports:

- 8080:8080

environment:

- NLP_CLIENT_PORT

- RACK_ENDPOINT

- PROSE_ENDPOINT

- AUTH_KEY

rake-app:

image: ${ISV_ACCOUNT}.dkr.ecr.${ISV_ECR_REGION}.amazonaws.com/rake-app:1.0.0

networks:

- nlp-demo

environment:

- RAKE_PORT

- AUTH_KEY

prose-app:

image: ${CUSTOMER_ACCOUNT}.dkr.ecr.${CUSTOMER_ECR_REGION}.amazonaws.com/prose-app:1.0.0

networks:

- nlp-demo

environment:

- PROSE_PORT

- AUTH_KEY



networks:

nlp-demo:



volumes:

data: {}

Execute the following commands to deploy the Docker Stack to Docker Swarm. Again, make sure you substitute the variable values below with your pseudo vendor and customer’s AWS Accounts and Regions. Additionally, API uses an API Key to protect all exposed endpoints, except the /health endpoint, across all three services. You should change the default CloudFormation template’s API Key parameter to something more secure.



export ISV_ACCOUNT=111222333444

export ISV_ECR_REGION=us-east-2

export CUSTOMER_ACCOUNT=999888777666

export CUSTOMER_ECR_REGION=us-west-2

export AUTH_KEY=SuP3r5eCRetAutHK3y



# don't change me

export NLP_CLIENT_PORT=8080

export RAKE_PORT=8080

export PROSE_PORT=8080

export RACK_ENDPOINT=

export PROSE_ENDPOINT=



docker swarm init

docker stack deploy --compose-file stack.yml nlp # change meexport ISV_ACCOUNT=export ISV_ECR_REGION=export CUSTOMER_ACCOUNT=export CUSTOMER_ECR_REGION=export AUTH_KEY=# don't change meexport NLP_CLIENT_PORT=8080export RAKE_PORT=8080export PROSE_PORT=8080export RACK_ENDPOINT= http://rake-app:8080 export PROSE_ENDPOINT= http://prose-app:8080 docker swarm initdocker stack deploy --compose-file stack.yml nlp

We can check the success of the deployment with the following commands.

docker stack ps nlp --no-trunc docker container ls

Below, we see an example of the expected terminal output.

With the Docker Stack, we can hit the nlp-client service directly on localhost:8080 . Unlike Fargate, which requires unique static ports for each container in the task, with Docker, we can choose run all the containers on the same port without conflict since only the nlp-client service is exposing port :8080 . Additionally, there is no load balancer in front of the Stack, unlike ECS, since we only have a single node in our Swarm, and thus a single container instance of each microservice.

To test that the images were pulled successfully and the Docker Stack is running, we can execute a curl command against any of the API endpoints, such as /keywords . Below, I am using jq to pretty-print the JSON response payload.

#change me

AUTH_KEY=SuP3r5eCRetAutHK3y



curl -s -X POST \

http://localhost:${NLP_CLIENT_PORT}/keywords \

-H 'Content-Type: application/json' \

-H "Authorization: Bearer ${AUTH_KEY}" \

-d '{"text": "The Internet is the global system of interconnected computer networks that use the Internet protocol suite to link devices worldwide."}' | jq

The resulting JSON payload should look similar to the following output. These results indicate that the nlp-client service was reached successfully and that it was then subsequently able to communicate with the rake-app service, whose container image originated from the vendor's ECR repository.

[

{

"candidate": "interconnected computer networks",

"score": 9

},

{

"candidate": "link devices worldwide",

"score": 9

},

{

"candidate": "internet protocol suite",

"score": 8

},

{

"candidate": "global system",

"score": 4

},

{

"candidate": "internet",

"score": 2

}

]

Creating Amazon ECS Environment

Although using Docker Swarm locally is a great way to understand how cross-account ECR access works, it is not a typical use case for deploying containerized applications on the AWS Platform. More often, you would use Amazon ECS, Amazon Elastic Kubernetes Service (EKS), or enterprise versions of third-party orchestrators, such as Docker Enterprise, RedHat OpenShift, or Rancher.

Using CloudFormation and some very convenient CloudFormation templates supplied by Amazon as a starting point, we will create a complete ECS environment for our application. First, we will create a VPC to house the ECS cluster and a public-facing NLB to front our ECS-based application, using the public-vpc.yml template.

aws cloudformation create-stack \

--stack-name public-vpc \

--template-body file://public-vpc.yml \

--capabilities CAPABILITY_NAMED_IAM

Next, we will create the ECS cluster and an Amazon ECS Task Definition, using the public-subnet-public-loadbalancer.yml template. Again, the Task Definition defines how ECS will deploy our application using AWS Fargate. Amazon Fargate allows you to run containers without having to manage servers or clusters. No EC2 instances to manage! Woot! Below, in the CloudFormation template, we see the ContainerDefinitions section of the TaskDefinition resource, container three container definitions. Note the three images and their ECR locations.

ContainerDefinitions:

- Name: nlp-client

Cpu: 256

Memory: 1024

Image: !Join ['.', [!Ref AWS::AccountId, 'dkr.ecr', !Ref AWS::Region, 'amazonaws.com/nlp-client:1.0.0']]

PortMappings:

- ContainerPort: !Ref ContainerPortClient

Essential: true

LogConfiguration:

LogDriver: awslogs

Options:

awslogs-region: !Ref AWS::Region

awslogs-group: !Ref CloudWatchLogsGroup

awslogs-stream-prefix: ecs

Environment:

- Name: NLP_CLIENT_PORT

Value: !Ref ContainerPortClient

- Name: RACK_ENDPOINT

Value: !Join [':', ['http://localhost', !Ref ContainerPortRake]]

- Name: PROSE_ENDPOINT

Value: !Join [':', ['http://localhost', !Ref ContainerPortProse]]

- Name: AUTH_KEY

Value: !Ref AuthKey

- Name: rake-app

Cpu: 256

Memory: 1024

Image: !Join ['.', [!Ref VendorAccountId, 'dkr.ecr', !Ref VendorEcrRegion, 'amazonaws.com/rake-app:1.0.0']]

Essential: true

LogConfiguration:

LogDriver: awslogs

Options:

awslogs-region: !Ref AWS::Region

awslogs-group: !Ref CloudWatchLogsGroup

awslogs-stream-prefix: ecs

Environment:

- Name: RAKE_PORT

Value: !Ref ContainerPortRake

- Name: AUTH_KEY

Value: !Ref AuthKey

- Name: prose-app

Cpu: 256

Memory: 1024

Image: !Join ['.', [!Ref AWS::AccountId, 'dkr.ecr', !Ref AWS::Region, 'amazonaws.com/prose-app:1.0.0']]

Essential: true

LogConfiguration:

LogDriver: awslogs

Options:

awslogs-region: !Ref AWS::Region

awslogs-group: !Ref CloudWatchLogsGroup

awslogs-stream-prefix: ecs

Environment:

- Name: PROSE_PORT

Value: !Ref ContainerPortProse

- Name: AUTH_KEY

Value: !Ref AuthKey

Execute the following command to create the ECS cluster and an Amazon ECS Task Definition using the CloudFormation template.

# change me

ISV_ACCOUNT=111222333444

ISV_ECR_REGION=us-east-2

AUTH_KEY=SuP3r5eCRetAutHK3y aws cloudformation create-stack \

--stack-name public-subnet-public-loadbalancer \

--template-body file://public-subnet-public-loadbalancer.yml \

--parameters \

ParameterKey=VendorAccountId,ParameterValue=${ISV_ACCOUNT} \

ParameterKey=VendorEcrRegion,ParameterValue=${ISV_ECR_REGION} \

ParameterKey=AuthKey,ParameterValue=${AUTH_KEY} \

--capabilities CAPABILITY_NAMED_IAM

Below, we see an example of the expected output from the CloudFormation management console.

The CloudFormation template does not enable CloudWatch Container Insights by default. Insights collects, aggregates, and summarizes metrics and logs from your containerized applications. To enable Insights, execute the following command.

aws ecs put-account-setting --name "containerInsights" --value "enabled"

Confirming the Cross-account Policy

If everything went right in the previous steps, we should now have an ECS cluster, running our containerized application, including the container built from the vendor’s Docker image. Below, we see an example of the ECS cluster, displayed in the management console.

Within the ECR cluster, we should observe a single running ECS Service. According to AWS, Amazon ECS allows you to run and maintain a specified number of instances of a task definition simultaneously in an Amazon ECS cluster. This is called a service.

We are running two instances of each container on ECS, thus two copies of the task within the single service. Each task runs it’s containers in a different Availability Zone for high-availability.

Drilling into the service, we should note the new NLB, associated with the new VPC, two public Subnets, and the corresponding Security Group.

Switching to the Task Definitions tab, we should see the details of our task. Note the three containers that compose the application. Note two are located in the customer’s ECR repositories, and one is located in the vendor’s ECR repository.

Drilling in a little farther, we will see the details of each container definition, including environment variables, passed from ECR to the container, and on to the actual Go-binary, running in the container.

Reaching our Application on ECS

Whereas with our earlier Docker Swarm example, the curl command was issued against /localhost , we now have the public-facing Network Load Balancer (NLB) in front of our ECS-based application. We will need to use the DNS name of your NLB as the host, to hit our application on ECS. The DNS address (A Record) can be obtained from the Load Balancer management console, as shown below, or from the Output tab of the public-vpc CloudFormation Stack

Another difference between the earlier Docker Swarm example and ECS is the port. Although the edge service, nlp-client , runs on port :8080 , the NLB acts as a reverse proxy, passing requests from port :80 on the NLB to port :8080 of the nlp-client container instances (actually, the shared ENI of the running task). For the sake of brevity and simplicity, I did not set up a custom DNS name for the NLB, nor HTTPS, as you normally would in Production.

To test our deployed ECS, we can use a tool like curl or Postman to test the API’s endpoints. Below, we see a POST against the /tokens endpoint, using Postman. If you are using Postman, don’t forget to you will need to add the API Key to the Auth tab. The Auth type will be ‘Bearer Token’.

Cleaning Up

To clean up the demonstration’s AWS resources and Docker Stack, run the following scripts in the appropriate AWS Accounts. Importantly, similar to S3, you must delete all the Docker images in the ECR repositories first, before deleting the repository, or else you will receive a CloudFormation error. This includes untagged images.

# customer account only

aws ecr batch-delete-image \

--repository-name nlp-client \

--image-ids imageTag=1.0.0



aws ecr batch-delete-image \

--repository-name prose-app \

--image-ids imageTag=1.0.0



aws cloudformation delete-stack \

--stack-name ecr-repo-nlp-client



aws cloudformation delete-stack \

--stack-name ecr-repo-prose-app



aws cloudformation delete-stack \

--stack-name public-subnet-public-loadbalancer



aws cloudformation delete-stack \

--stack-name public-vpc docker stack rm nlp

# vendor account only

aws ecr batch-delete-image \

--repository-name rake-app \

--image-ids imageTag=1.0.0



aws cloudformation delete-stack \

--stack-name ecr-repo-rake-app

# both accounts

aws cloudformation delete-stack \

--stack-name developer-user-group

In the preceding post, we saw one-way multiple AWS Accounts can share ECR-based Docker Images. There are variations and restrictions to the configuration of the ECR Repository Policies, depending on the deployment tools you are using, such as AWS CodeBuild, AWS CodeDeploy, or AWS Elastic Beanstalk. AWS does a good job of providing some examples in their documentation, including Amazon ECR Repository Policy Examples and Amazon Elastic Container Registry Identity-Based Policy Examples.

All opinions expressed in this post are my own and not necessarily the views of my current or past employers or their clients.