ECR Is Finally Usable!

AWS Finally Listens: After 5+ Years, ECR Gets Two Game-Changing Features

The Long Wait Is Over

In late 2025 and early 2026, Amazon Web Services released two of the most requested features in ECR’s history:

  1. Automatic repository creation on image push (December 19th, 2025)
  2. Cross-repository layer sharing via blob mounting (January 20th, 2026)

For those of us who have been managing container workflows on AWS, these announcements are nothing short of a miracle. These two features alone transform ECR from “that registry we avoid” to “a genuinely competitive option.”

For over five years, developers have been asking, begging, and practically pleading for these features. The infamous GitHub issues accumulated:

Feature Issue Opened Upvotes Wait Time
Create on Push #853 April 2020 700+ 5+ years
Layer Sharing #531 October 2019 300+ 6+ years

Many had lost hope. But AWS finally delivered—on both fronts.


Part 1: Automatic Repository Creation on Push

The Problem

If you’ve ever worked with container registries, you know that the standard workflow is simple:

  1. Build your image
  2. Tag it with your registry URL
  3. Push it

That’s it. Docker Hub, Google Container Registry (GCR), Azure Container Registry (ACR), Quay, Harbor, Artifactory, Nexus—every single major container registry works this way. If the repository doesn’t exist, it gets created automatically.

Except ECR.

With Amazon ECR, before you could push an image, you had to:

  1. Pre-create the repository using the AWS Console, CLI, CloudFormation, or Terraform
  2. Configure the repository settings (encryption, lifecycle policies, permissions)
  3. Then push your image

This might seem like a minor inconvenience, but it created massive headaches in real-world scenarios:

The Developer Experience Nightmare

Consider a development team practicing continuous integration. Every time a developer creates a new microservice, they need to:

  1. Request a new ECR repository (or have permissions to create one)
  2. Wait for the infrastructure team to provision it (if they don’t have permissions)
  3. Update their CI/CD pipeline to reference the new repository
  4. Finally push their image

Compare this to GCR where you just… push. The repository appears. Done.

CI/CD Pipeline Complexity

CI/CD pipelines became unnecessarily complex. You couldn’t just have a simple docker push command. You needed error handling, repository existence checks, and creation logic wrapped around every push:

# The ECR workaround everyone hated
aws ecr describe-repositories --repository-names "${REPO_NAME}" 2>/dev/null || \
    aws ecr create-repository --repository-name "${REPO_NAME}"
docker push "${ECR_URL}/${REPO_NAME}:${TAG}"

This added complexity, potential race conditions, and additional IAM permissions requirements.

Platform Integration Challenges

Modern platforms like Kubernetes, Tanzu Application Platform (RIP), and others often need to push images dynamically. I wrote about this exact challenge back in 2022 in my blog post TAP with ECR – Crossplane and Kyverno to the Rescue, where I detailed the elaborate workarounds we needed to implement just to make Tanzu Application Platform (RIP) work with ECR.

The solution involved:

  • Crossplane to dynamically provision ECR repositories as Kubernetes resources
  • Kyverno policies to automatically trigger repository creation based on workload definitions
  • Complex RBAC and IAM role mappings

It worked, but it was a lot of infrastructure complexity just to achieve what should have been a built-in feature.

The Community Frustration

The comments on the GitHub issue tell the story:

“This is the largest blocker for us in fully transitioning to ECR. Unbelievable it doesn’t exist yet…” — @MichaelErmer, July 2022

“We really need this feature as well, otherwise we simply can’t use ECR.” — @vrabbi (yes, me!), October 2022

“This feature works really well in Artifactory and Nexus… if you are coming from one of these other products where this feature is baked into docker push.” — @sputmayer, December 2021

“Just so you know, this works fine in Google Cloud Container Registry.” — @simonlsk, May 2023

The Solution: Repository Creation Templates with Create on Push

AWS has solved this problem elegantly with Repository Creation Templates that now support a Create on Push applied-for type.

How It Works

The feature works in three key steps:

  1. Create a Repository Creation Template: Define the settings you want applied to any new repositories (encryption, lifecycle policies, permissions, tags, etc.)

  2. Specify “Create on Push” as an Applied-For Type: This tells ECR to use this template when someone pushes to a repository that doesn’t exist

  3. Push Your Images: When you push to a non-existent repository, ECR automatically creates it with your template settings applied

Here’s the workflow visualized:

Developer pushes image to: 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp/frontend:v1.0
                                    |
                                    v
                    Repository "myapp/frontend" doesn't exist
                                    |
                                    v
              ECR checks for matching repository creation template
                                    |
                                    v
        Template with prefix "myapp" found with CREATE_ON_PUSH enabled
                                    |
                                    v
        Repository created with template settings (encryption, policies, etc.)
                                    |
                                    v
                          Image pushed successfully!

Template Configuration Options

Repository creation templates give you control over:

Setting Description
Prefix Repository namespace prefix (e.g., prod/, myapp/) or ROOT for all
Applied For PULL_THROUGH_CACHE, CREATE_ON_PUSH, or REPLICATION
Image Tag Mutability MUTABLE or IMMUTABLE
Encryption AES-256 (default) or KMS with custom keys
Repository Policy IAM resource-based access control
Lifecycle Policy Automatic image cleanup rules
Resource Tags Metadata for organization and cost tracking

Part 2: Cross-Repository Layer Sharing (Blob Mounting)

The Problem

ECR’s repository-per-image model created another significant pain point: no layer sharing between repositories.

Container images are built in layers. When you have dozens of microservices all built on the same base image (say, node:18-alpine or your company’s custom base image), those base layers are identical across all your service images.

In most container registries, these common layers are stored once and shared. When you push a new image, the registry recognizes “I already have this layer” and skips the upload.

Not ECR. Every repository was an island. Push the same base layer to 50 different repositories? ECR would happily store 50 copies and charge you for all of them.

The GitHub issue #531, opened in October 2019, captured the frustration:

“Currently ECR doesn’t cache image layers between repositories and with ECR’s model of creating a repo per image this leads to quite poor performance, especially in situations where there are many images being built on a common base-image.” — @jespersoderlund

The impact was significant:

  • Slow pushes: Every image push uploaded the full image, even when layers already existed in other repositories
  • Increased storage costs: Duplicate layers stored across repositories
  • Poor replication performance: Cross-region replication had to transfer duplicate data
  • Frustrated platform teams: Managing hundreds of microservices meant dealing with these inefficiencies at scale

The Solution: Blob Mounting

On January 20th, 2026, AWS released blob mounting for ECR. This feature enables cross-repository layer sharing within a registry.

How It Works

When blob mounting is enabled:

  1. During an image push, ECR checks if the layer already exists in any repository within the same registry
  2. If found, ECR “mounts” (references) the existing layer instead of storing a duplicate
  3. The push completes faster, and you don’t pay for duplicate storage
Push image to: myapp/service-a (has layers A, B, C)
                    |
                    v
        Layer A already exists in myapp/service-b
                    |
                    v
        ECR mounts existing Layer A (no re-upload needed)
                    |
                    v
        Layers B and C uploaded normally
                    |
                    v
        Push completes faster with less storage used

Key Concepts

  • Blob mounting only works within the same registry (same account and region)
  • Repositories must use identical encryption type and keys
  • Blob mounting is not supported for images created via pull through cache
  • If you disable blob mounting later, existing mounted layers continue to work

Enabling Blob Mounting

Using the AWS Console

  1. Open the Amazon ECR console
  2. Navigate to Private registry > Feature & Settings > Blob mounting
  3. Click Enable

Using the AWS CLI

aws ecr put-account-setting --name BLOB_MOUNTING --value ENABLED

Using Terraform

The Terraform support is merged and will be available in the next AWS provider release:

resource "aws_ecr_account_setting" "blob_mounting" {
  name  = "BLOB_MOUNTING"
  value = "ENABLED"
}

Getting Started with Create on Push

Using the AWS Console

  1. Open the Amazon ECR console
  2. Navigate to Private registry > Repository creation templates
  3. Click Create template
  4. Choose A specific prefix or Any prefix in your ECR registry
  5. For Applied for, select CREATE_ON_PUSH (and optionally PULL_THROUGH_CACHE and REPLICATION)
  6. Configure your desired settings (encryption, lifecycle policies, etc.)
  7. Click Create

Using the AWS CLI

Create a template configuration file create-on-push-template.json:

{
  "prefix": "ROOT",
  "description": "Default template for all repositories created on push",
  "appliedFor": ["CREATE_ON_PUSH"],
  "imageTagMutability": "MUTABLE",
  "encryptionConfiguration": {
    "encryptionType": "AES256"
  },
  "lifecyclePolicy": "{\"rules\":[{\"rulePriority\":1,\"description\":\"Keep last 30 images\",\"selection\":{\"tagStatus\":\"any\",\"countType\":\"imageCountMoreThan\",\"countNumber\":30},\"action\":{\"type\":\"expire\"}}]}"
}

Then apply it:

aws ecr create-repository-creation-template \
    --cli-input-json file://create-on-push-template.json

Using Terraform

The aws_ecr_repository_creation_template resource was added in AWS provider version 6.28.0. Here are practical examples:

Basic Create on Push Template

resource "aws_ecr_repository_creation_template" "default" {
  prefix       = "ROOT"
  description  = "Default settings for all auto-created repositories"
  applied_for  = ["CREATE_ON_PUSH"]

  image_tag_mutability = "MUTABLE"

  encryption_configuration {
    encryption_type = "AES256"
  }
}

Production Template with Full Configuration

resource "aws_ecr_repository_creation_template" "production" {
  prefix       = "prod"
  description  = "Production repositories with strict settings"
  applied_for  = ["CREATE_ON_PUSH", "REPLICATION"]

  image_tag_mutability = "IMMUTABLE"

  encryption_configuration {
    encryption_type = "KMS"
    kms_key         = aws_kms_key.ecr.arn
  }

  resource_tags = {
    Environment = "production"
    ManagedBy   = "terraform"
    CostCenter  = "platform-team"
  }

  # Custom IAM role required when using KMS or resource tags
  custom_role_arn = aws_iam_role.ecr_template_role.arn

  lifecycle_policy = jsonencode({
    rules = [
      {
        rulePriority = 1
        description  = "Keep last 50 tagged images"
        selection = {
          tagStatus     = "tagged"
          tagPrefixList = ["v"]
          countType     = "imageCountMoreThan"
          countNumber   = 50
        }
        action = {
          type = "expire"
        }
      },
      {
        rulePriority = 2
        description  = "Expire untagged images older than 7 days"
        selection = {
          tagStatus   = "untagged"
          countType   = "sinceImagePushed"
          countUnit   = "days"
          countNumber = 7
        }
        action = {
          type = "expire"
        }
      }
    ]
  })

  repository_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowPull"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }
        Action = [
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "ecr:BatchCheckLayerAvailability"
        ]
      }
    ]
  })
}

# KMS key for repository encryption
resource "aws_kms_key" "ecr" {
  description             = "KMS key for ECR repository encryption"
  deletion_window_in_days = 7
  enable_key_rotation     = true
}

# IAM role for repository creation template
resource "aws_iam_role" "ecr_template_role" {
  name = "ecr-repository-creation-template-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "ecr.amazonaws.com"
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy" "ecr_template_policy" {
  name = "ecr-template-policy"
  role = aws_iam_role.ecr_template_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "ecr:CreateRepository",
          "ecr:TagResource",
          "ecr:PutLifecyclePolicy",
          "ecr:SetRepositoryPolicy"
        ]
        Resource = "*"
      },
      {
        Effect = "Allow"
        Action = [
          "kms:Encrypt",
          "kms:Decrypt",
          "kms:GenerateDataKey*"
        ]
        Resource = aws_kms_key.ecr.arn
      }
    ]
  })
}

Multiple Environment Templates

locals {
  environments = {
    dev = {
      prefix           = "dev"
      tag_mutability   = "MUTABLE"
      encryption_type  = "AES256"
      image_retention  = 10
    }
    staging = {
      prefix           = "staging"
      tag_mutability   = "MUTABLE"
      encryption_type  = "AES256"
      image_retention  = 20
    }
    prod = {
      prefix           = "prod"
      tag_mutability   = "IMMUTABLE"
      encryption_type  = "KMS"
      image_retention  = 100
    }
  }
}

resource "aws_ecr_repository_creation_template" "environment" {
  for_each = local.environments

  prefix       = each.value.prefix
  description  = "${each.key} environment repositories"
  applied_for  = ["CREATE_ON_PUSH"]

  image_tag_mutability = each.value.tag_mutability

  encryption_configuration {
    encryption_type = each.value.encryption_type
    kms_key         = each.value.encryption_type == "KMS" ? aws_kms_key.ecr.arn : null
  }

  custom_role_arn = each.value.encryption_type == "KMS" ? aws_iam_role.ecr_template_role.arn : null

  resource_tags = {
    Environment = each.key
  }

  lifecycle_policy = jsonencode({
    rules = [
      {
        rulePriority = 1
        description  = "Keep last ${each.value.image_retention} images"
        selection = {
          tagStatus   = "any"
          countType   = "imageCountMoreThan"
          countNumber = each.value.image_retention
        }
        action = {
          type = "expire"
        }
      }
    ]
  })
}

Complete Terraform Configuration: Both Features

Here’s a complete Terraform configuration that enables both new features:

# Enable blob mounting for cross-repository layer sharing
resource "aws_ecr_account_setting" "blob_mounting" {
  name  = "BLOB_MOUNTING"
  value = "ENABLED"
}

# Create a default repository creation template for create-on-push
resource "aws_ecr_repository_creation_template" "default" {
  prefix       = "ROOT"
  description  = "Default settings for all auto-created repositories"
  applied_for  = ["CREATE_ON_PUSH", "PULL_THROUGH_CACHE", "REPLICATION"]

  image_tag_mutability = "MUTABLE"

  encryption_configuration {
    encryption_type = "AES256"
  }

  lifecycle_policy = jsonencode({
    rules = [
      {
        rulePriority = 1
        description  = "Keep last 50 images"
        selection = {
          tagStatus   = "any"
          countType   = "imageCountMoreThan"
          countNumber = 50
        }
        action = {
          type = "expire"
        }
      }
    ]
  })
}

IAM Permissions Required

To create and manage repository creation templates, ensure your IAM principal has these permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:CreateRepositoryCreationTemplate",
        "ecr:UpdateRepositoryCreationTemplate",
        "ecr:DescribeRepositoryCreationTemplates",
        "ecr:DeleteRepositoryCreationTemplate",
        "ecr:CreateRepository",
        "ecr:PutLifecyclePolicy",
        "ecr:SetRepositoryPolicy",
        "ecr:PutAccountSetting",
        "ecr:GetAccountSetting"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "arn:aws:iam::*:role/ecr-*"
    }
  ]
}

Important Considerations

Template Prefix Matching

Templates are matched based on the most specific prefix. For example:

  • A repository named prod/team/myapp would match a template with prefix prod/team over one with just prod
  • Use ROOT as the prefix to create a catch-all template for repositories that don’t match any other template

Templates Don’t Affect Existing Repositories

Repository creation templates only apply when ECR creates a new repository. Existing repositories are not modified. If you need to update existing repositories, you’ll need to do that separately.

KMS and Tags Require an IAM Role

If your template uses KMS encryption or resource tags, you must specify a customRoleArn. This role is assumed by ECR when creating repositories. Without it, repository creation will fail.

Blob Mounting Encryption Requirements

For blob mounting to work between repositories, they must use identical encryption types and keys. If you have repositories with different encryption configurations, layers cannot be shared between them.


Summary

After more than five and six years of waiting respectively, ECR has finally caught up with every other container registry on the planet. These two features together transform ECR from a clunky, DevOps-hostile service into something genuinely competitive.

What we gained with Create on Push:

  • Seamless push workflows—just push, the repository appears
  • Consistent repository settings across your organization
  • Simplified CI/CD pipelines—no more pre-creation scripts
  • Better platform integrations (goodbye Crossplane workarounds!)
  • Parity with other major container registries

What we gained with Blob Mounting:

  • Faster image pushes by reusing existing layers
  • Reduced storage costs by eliminating duplicate layers
  • Improved replication performance
  • Better efficiency for microservices architectures with shared base images

What’s different from other registries:

  • You need to create at least one repository creation template first
  • You need to explicitly enable blob mounting at the registry level
  • Templates give you more control over default settings than most registries offer

This is exactly what the community asked for, and AWS delivered. For anyone who commented on issues #853 and #531, voted them up, or wrote workarounds like I did—our patience has been rewarded.

Now if you’ll excuse me, I have some Crossplane and Kyverno resources to delete.


Resources

Create on Push

Blob Mounting

Related


Posted on January 2026

Leave a Reply

Discover more from vRabbi's Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading