Skip to main content

Build and push container images to Amazon ECR

16 min

Amazon Elastic Container Registry (ECR) is a managed Docker container registry that makes it easy to store, manage, and deploy Docker container images. ECR supports private Docker registries with resource-based permissions using AWS IAM, so specific users and instances can access images. Using ECR simplifies going from development to production, and eliminates the need to operate your own container repositories or worry about scaling the underlying infrastructure, while hosting your images in a highly available and scalable architecture.

Overview#

The AWSx ECR components simplify the provisioning of new ECR repositories, integrate with the AWSx ECS and EKS components to ease deployment of new application containers to your ECS, “Fargate”, and/or Kubernetes clusters, and even support building and deploying Docker images from your developer desktop or CI/CD workflows.

Provisioning an ECR repository#

Each AWS account automatically has an ECR registry, and within each registry, you can create any number of repositories to actually contain your Docker images.

To create a new ECR repository, allocate an instance of the awsx.ecr.Repository class:

import * as awsx from "@pulumi/awsx";
const repo = new awsx.ecr.Repository("repo");
export const url = repo.url;

The exported url is what we will use to push and pull images to and from the newly created repository, either using the Docker CLI or through infrastructure as code in our Pulumi program.

Building and publishing container images#

Amazon ECR stores images inside of the repositories you create. You can use the Docker CLI to push and pull images explicitly, using the build, push, and pull commands, targeting the repository’s URL. Alternatively, you can use your Pulumi program to build and publish container images as part of your Pulumi deployment, and consume them from ECS or EKS directly.

Building and publishing images manually using the Docker CLI#

All repositories in your account’s ECR registry will have a URL of the form <aws_account_id>.dkr.ecr.<region>.amazonaws.com/<repo>, where <aws_account_id> is your AWS account ID, <region> is the location for the repository, and <repo> is the name given to the repository. In the above example, the resulting URL is exported and printed to the console.

To build and publish a new Docker image to such a repository, first retrieve your container image in the usual way, e.g. either using docker build or docker pull.

Store the repository URL from your Pulumi stack output in a variable for use in subsequent commands:

Terminal window
REPO_URL=$(pulumi stack output url)

The image then needs to be tagged with the URL of the repository you’re publishing to. This can be done using docker build’s -t argument while building the image:

Terminal window
docker build -t $REPO_URL .

Alternatively, this can be done by tagging the image with docker tag after building or pulling it. For example, if the image ID to tag is e9ae3c220b23, then we would run the following:

Terminal window
docker tag e9ae3c220b23 $REPO_URL

By default, this tag will be tagged as latest; if you’d like to tag it using something else, do so as usual:

Terminal window
docker tag e9ae3c220b23 $REPO_URL:v2.0

After building and tagging, we then need to authenticate with the ECR registry. Each authentication token covers a single registry and lasts 12 hours. The AWS CLI provides an easy way to do this:

Terminal window
aws ecr get-login-password | docker login --username AWS --password-stdin $(echo $REPO_URL | cut -d/ -f1)

For more information on authentication, see Registry Authentication

Finally, after building, tagging, and logging in, we are ready to push to our repository:

Terminal window
docker push $REPO_URL

Afterwards, we can then pull the image from the registry by authenticating and pulling from the repository URL.

Building and publishing images automatically in code#

Instead of using the Docker CLI directly, Pulumi supports building, publishing, and consuming Docker images entirely from code. This lets you version and deploy container changes easily alongside the supporting infrastructure.

In the following example, creating an Image resource will build an image from the “./app” directory (relative to our project and containing Dockerfile), and publish it to our ECR repository provisioned above.

import * as pulumi from "@pulumi/pulumi";
import * as awsx from "@pulumi/awsx";
const repository = new awsx.ecr.Repository("repository", {
forceDelete: true,
});
const image = new awsx.ecr.Image("image", {
repositoryUrl: repository.url,
context: "./app",
platform: "linux/amd64",
});
export const url = repository.url;

The exported image URL can then be used anywhere you’d normally use a Docker image name. For example, you can run it:

Terminal window
docker run -p 80:80 $(pulumi stack output url):latest

As we will see below, this can also be consumed from your container orchestrator, to run the container as a service.

Deleting images#

If you are done using an image, you can delete it from your repository. This can be done by defining a lifecycle policy or manually using the AWS CLI. For more information on how to manually delete an image, see the ECR documentation on Deleting an Image.

Using a private repository from your container orchestrator#

To use your ECR images with Amazon ECS and EKS, use the full repository name as the image name. As seen above, this is of the form <aws_account_id>.dkr.ecr.<region>.amazonaws.com/<repo>[:<tag>], where the <tag> is optional (it defaults to latest). The container instances require IAM permissions which are typically enabled by default.

Consuming a private repository from ECS#

To use your private repository from an ECS task definition, reference it like so:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
const repo = new awsx.ecr.Repository("repo", {
forceDelete: true,
});
const image = new awsx.ecr.Image("image", {
repositoryUrl: repo.url,
context: "./app",
platform: "linux/amd64",
});
const cluster = new aws.ecs.Cluster("cluster");
const lb = new awsx.lb.ApplicationLoadBalancer("lb");
const service = new awsx.ecs.FargateService("service", {
cluster: cluster.arn,
assignPublicIp: true,
taskDefinitionArgs: {
container: {
name: "my-service",
image: image.imageUri,
cpu: 128,
memory: 512,
essential: true,
portMappings: [
{
containerPort: 80,
targetGroup: lb.defaultTargetGroup,
},
],
},
},
});
export const url = pulumi.interpolate`http://${lb.loadBalancer.dnsName}`;

For information about ECS, refer to the ECS tutorial. For information about consuming ECR images from ECS services specifically, see Using Amazon ECR Images with Amazon ECR.

Consuming a private repository from EKS#

To use your private repository from a Kubernetes service, such as one using EKS, reference it like so:

import * as pulumi from "@pulumi/pulumi";
import * as awsx from "@pulumi/awsx";
import * as eks from "@pulumi/eks";
import * as kubernetes from "@pulumi/kubernetes";
const appName = "my-app";
const repository = new awsx.ecr.Repository("repository", {
forceDelete: true,
});
const image = new awsx.ecr.Image("image", {
repositoryUrl: repository.url,
context: "./app",
platform: "linux/amd64",
});
const cluster = new eks.Cluster("cluster");
const clusterProvider = new kubernetes.Provider("clusterProvider", {
kubeconfig: cluster.kubeconfig,
enableServerSideApply: true,
});
const deployment = new kubernetes.apps.v1.Deployment(
"deployment",
{
metadata: {
labels: {
appClass: appName,
},
},
spec: {
replicas: 2,
selector: {
matchLabels: {
appClass: appName,
},
},
template: {
metadata: {
labels: {
appClass: appName,
},
},
spec: {
containers: [
{
name: appName,
image: image.imageUri,
ports: [
{
name: "http",
containerPort: 80,
},
],
},
],
},
},
},
},
{
provider: clusterProvider,
},
);
const service = new kubernetes.core.v1.Service(
"service",
{
metadata: {
labels: {
appClass: appName,
},
},
spec: {
type: "LoadBalancer",
selector: {
appClass: appName,
},
ports: [
{
port: 80,
targetPort: "http",
},
],
},
},
{
provider: clusterProvider,
},
);
export const url = service.status.apply(status => status?.loadBalancer?.ingress?.[0]?.hostname);

This example uses the EKS package to provision the cluster and the Kubernetes provider to deploy the container image to it.

For information about EKS, refer to the EKS tutorial.

IAM permissions required to use ECR#

For the above examples to work, the container instances powering your ECS or EKS cluster need proper IAM policy permissions to access your Amazon ECR registry. The following example defines such an IAM policy:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:GetAuthorizationToken"
],
"Resource": "*"
}
]
}

See the AWS IAM tutorial for instructions on how to manage such policies.

Managing container image lifecycles using policies#

ECR lifecycle policies allow you to specify the lifecycle management of images in a repository. A lifecycle policy is a set of one or more rules, where each rule defines an action for Amazon ECR. The actions apply to images that contain tags prefixed with the given strings. This allows the automation of cleaning up unused images, for example expiring images based on age or count. You should expect that after creating a lifecycle policy the affected images are expired within 24 hours.

The AWSx ECR components make it easy to configure a repository’s lifecycle policy, using the lifeCyclePolicyArgs property on the Repository class’s constructor. Using this property, there are two main ways to control how an image is purged from the repository:

  1. Once a maximum number of images has been reached (maximumNumberOfImages).
  2. Once an image reaches a maximum allowed age (maximumAgeLimit).

Lifecycle policy rules#

For more details, refer to Amazon ECR Lifecycle Policies, however we will now examine a number of examples to demonstrate how lifecycle policies are applied.

Additional ECR resources#

For more information about Amazon ECR, see the following:

Related

The infrastructure as code platform for any cloud.