Managing AWS ECR with Terraform
A comprehensive guide to setting up Amazon Elastic Container Registry (ECR) using Terraform Infrastructure as Code
Managing AWS ECR with Terraform
Amazon Elastic Container Registry (ECR) is a fully managed container registry service. This guide shows how to set up ECR using Terraform.
Prerequisites
- AWS CLI configured
- Terraform installed
- Docker installed locally
- Container images to store
Project Structure
aws-ecr-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars
Basic ECR Configuration
# main.tf
provider "aws" {
region = var.aws_region
}
# ECR Repository
resource "aws_ecr_repository" "main" {
name = var.repository_name
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
tags = {
Environment = var.environment
}
}
# KMS Key for Encryption
resource "aws_kms_key" "ecr" {
description = "KMS key for ECR encryption"
deletion_window_in_days = 7
enable_key_rotation = true
tags = {
Environment = var.environment
}
}
# Repository Policy
resource "aws_ecr_repository_policy" "main" {
repository = aws_ecr_repository.main.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowPull"
Effect = "Allow"
Principal = {
AWS = var.allowed_account_arns
}
Action = [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
]
}
]
})
}
# Lifecycle Policy
resource "aws_ecr_lifecycle_policy" "main" {
repository = aws_ecr_repository.main.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 30 production images"
selection = {
tagStatus = "tagged"
tagPrefixList = ["prod"]
countType = "imageCountMoreThan"
countNumber = 30
}
action = {
type = "expire"
}
},
{
rulePriority = 2
description = "Keep last 10 development images"
selection = {
tagStatus = "tagged"
tagPrefixList = ["dev"]
countType = "imageCountMoreThan"
countNumber = 10
}
action = {
type = "expire"
}
},
{
rulePriority = 3
description = "Remove untagged images after 7 days"
selection = {
tagStatus = "untagged"
countType = "sinceImagePushed"
countUnit = "days"
countNumber = 7
}
action = {
type = "expire"
}
}
]
})
}
Variables Configuration
# variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "repository_name" {
description = "Name of the ECR repository"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "allowed_account_arns" {
description = "List of AWS account ARNs allowed to pull images"
type = list(string)
default = []
}
Best Practices
-
Repository Management
- Use immutable tags
- Enable image scanning
- Implement lifecycle policies
- Regular cleanup
-
Security
- Enable encryption
- Use proper IAM roles
- Implement repository policies
- Regular security reviews
-
Cost Optimization
- Monitor storage usage
- Clean up unused images
- Use appropriate lifecycle rules
- Regular cost reviews
-
Performance
- Use image layers effectively
- Optimize image size
- Consider multi-region replication
- Regular performance reviews
Cross-Account Access
# Repository Policy for Cross-Account Access
resource "aws_ecr_repository_policy" "cross_account" {
repository = aws_ecr_repository.main.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCrossAccountPull"
Effect = "Allow"
Principal = {
AWS = var.cross_account_arns
}
Action = [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
]
},
{
Sid = "AllowCrossAccountPush"
Effect = "Allow"
Principal = {
AWS = var.cross_account_push_arns
}
Action = [
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
]
}
]
})
}
Replication Configuration
# Replication Configuration
resource "aws_ecr_replication_configuration" "main" {
replication_configuration {
rule {
destination {
region = var.destination_region
registry_id = var.destination_account_id
}
repository_filter {
filter = "prod-*"
filter_type = "PREFIX_MATCH"
}
}
}
}
Monitoring Configuration
# CloudWatch Dashboard
resource "aws_cloudwatch_dashboard" "ecr" {
dashboard_name = "${var.repository_name}-dashboard"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 6
properties = {
metrics = [
["AWS/ECR", "RepositoryPullCount", "RepositoryName", aws_ecr_repository.main.name],
["AWS/ECR", "RepositoryPushCount", "RepositoryName", aws_ecr_repository.main.name]
]
period = 300
stat = "Sum"
region = var.aws_region
title = "ECR Repository Activity"
}
}
]
})
}
# CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "image_scan_findings" {
alarm_name = "${var.repository_name}-scan-findings"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "ImageScanFindingsCount"
namespace = "AWS/ECR"
period = "300"
statistic = "Maximum"
threshold = "0"
alarm_description = "This metric monitors ECR image scan findings"
alarm_actions = [var.sns_topic_arn]
dimensions = {
Repository = aws_ecr_repository.main.name
}
}
Deployment Steps
- Initialize Terraform:
terraform init
- Plan the deployment:
terraform plan
- Apply the configuration:
terraform apply
Clean Up
Remove all resources when done:
terraform destroy
Common Use Cases
- Multi-Environment Setup
resource "aws_ecr_repository" "environments" {
for_each = toset(["dev", "staging", "prod"])
name = "${var.repository_name}-${each.key}"
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
tags = {
Environment = each.key
}
}
resource "aws_ecr_lifecycle_policy" "environments" {
for_each = aws_ecr_repository.environments
repository = each.value.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last ${each.key == "prod" ? "30" : "10"} images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = each.key == "prod" ? 30 : 10
}
action = {
type = "expire"
}
}
]
})
}
- CI/CD Integration
# IAM Role for CI/CD
resource "aws_iam_role" "cicd" {
name = "${var.repository_name}-cicd-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "codebuild.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "cicd" {
name = "${var.repository_name}-cicd-policy"
role = aws_iam_role.cicd.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:DescribeImages",
"ecr:BatchGetImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
]
Resource = aws_ecr_repository.main.arn
}
]
})
}
Advanced Features
# Pull Through Cache Configuration
resource "aws_ecr_pull_through_cache_rule" "upstream" {
ecr_repository_prefix = "upstream"
upstream_registry_url = "public.ecr.aws"
}
# Registry Policy
resource "aws_ecr_registry_policy" "main" {
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowPull"
Effect = "Allow"
Principal = {
AWS = "*"
}
Action = [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
]
Condition = {
StringLike = {
"aws:PrincipalOrgID": data.aws_organizations_organization.current.id
}
}
}
]
})
}
Conclusion
This setup provides a comprehensive foundation for deploying ECR using Terraform. Remember to:
- Plan your repository structure carefully
- Implement proper security measures
- Monitor repository usage and costs
- Keep your configurations versioned
- Test thoroughly before production deployment
The complete code can be customized based on your specific requirements and use cases.