테라폼 - ECR 구현하기
Docker 이미지를 저장할 컨테이너 이미지 레지스트리 만들기
이전 글에서는 Terraform으로 ALB와 Target Group을 구현하는 방법을 정리했다.
이번 글에서는 AWS에서 Docker 이미지를 저장할 때 사용하는 ECR을 Terraform으로 구현해보려 한다.
ECR은 Elastic Container Registry의 약자다.
쉽게 말하면 AWS에서 제공하는 Docker 이미지 저장소다.
Docker Image
→ ECR Repository
→ ECS / Lambda / EC2에서 사용
ECR은 ECS Fargate, Lambda Container Image, EC2 기반 Docker 배포에서 자주 사용된다.
다만 이 글에서는 Docker 이미지를 빌드하고 push하는 배포 흐름까지 깊게 다루지는 않는다.
Terraform은 ECR 저장소를 만들고, 이미지를 빌드하고 push하는 것은 CI/CD가 담당하는 영역으로 나누어 생각하는 것이 좋다.
목차
- 1. ECR이란 무엇인가
- 2. ECR은 어디에 사용될까?
- 3. ECR과 배포의 역할 분리
- 4. ECR을 만들 때 고려할 설정
- 5. 기본 ECR Repository 만들기
- 6. Image Tag Mutability 설정
- 7. Image Scanning 설정
- 8. Encryption 설정
- 9. Lifecycle Policy 설정
- 10. Repository Policy와 IAM 권한
- 11. ECS에서 사용할 Repository URL 출력하기
- 12. 실전 예제: 애플리케이션용 ECR 구성
- 13. 의존성 흐름
- 14. 자주 하는 실수
- 15. 마무리
1. ECR이란 무엇인가
ECR은 Elastic Container Registry의 약자다.
컨테이너 이미지를 저장하고 관리하는 AWS의 이미지 레지스트리 서비스다.
로컬에서 Docker 이미지를 만들면 보통 다음 흐름으로 사용한다.
Dockerfile
→ docker build
→ Docker Image
→ docker push
→ Image Registry
여기서 AWS 환경에서 사용하는 이미지 저장소가 ECR이다.
Docker Image
→ Amazon ECR
→ ECS / Lambda / EC2
예를 들어 Spring Boot 애플리케이션을 Docker 이미지로 만들었다면, 그 이미지를 ECR에 push하고 ECS Fargate에서 해당 이미지를 pull해서 실행할 수 있다.
Spring Boot App
→ Docker Image
→ ECR
→ ECS Fargate Task
2. ECR은 어디에 사용될까?
ECR은 컨테이너 기반 서비스를 사용할 때 자주 등장한다.
| 사용처 | 설명 |
|---|---|
| ECS Fargate | Task Definition에서 ECR 이미지 URL을 사용 |
| ECS EC2 | EC2 기반 ECS Cluster에서 ECR 이미지를 pull |
| Lambda Container Image | Lambda 함수를 컨테이너 이미지로 배포할 때 사용 |
| EC2 Docker 배포 | EC2 내부에서 docker pull로 이미지 실행 |
| CI/CD | GitHub Actions, CodeBuild 등이 이미지를 push |
ECR 자체는 애플리케이션을 실행하지 않는다.
ECR은 이미지를 저장하고, ECS나 Lambda 같은 실행 환경이 그 이미지를 가져가서 실행한다.
ECR
→ 이미지 저장소
ECS / Lambda / EC2
→ 이미지 실행 환경
3. ECR과 배포의 역할 분리
ECR 글에서 헷갈리기 쉬운 부분이 있다.
바로 ECR 구현과 Docker 이미지 배포를 섞어서 생각하는 것이다.
Terraform이 담당하기 좋은 부분은 다음과 같다.
ECR Repository 생성
Lifecycle Policy 설정
Image Scanning 설정
Tag Mutability 설정
Repository Policy 설정
Repository URL output
반면 CI/CD가 담당하기 좋은 부분은 다음과 같다.
docker build
docker tag
aws ecr get-login-password
docker push
ECS service update
배포 후 health check
따라서 이 글에서는 ECR 리소스를 Terraform으로 만드는 데 집중한다.
이미지를 빌드하고 ECR에 push하는 흐름은 이후 GitHub Actions 또는 CI/CD 시리즈에서 따로 다루는 것이 자연스럽다.
ECR 저장소 생성은 Terraform 영역이고, 이미지 push와 서비스 배포는 CI/CD 영역으로 나누는 것이 좋다.
4. ECR을 만들 때 고려할 설정
ECR Repository를 만들 때는 단순히 저장소 이름만 정하는 것으로 끝나지 않는다.
다음 설정을 함께 고려하는 것이 좋다.
| 설정 | Terraform 속성 / 리소스 | 역할 |
|---|---|---|
| Repository | aws_ecr_repository | ECR 저장소 본체 |
| Tag Mutability | image_tag_mutability | 같은 태그 덮어쓰기 허용 여부 |
| Image Scan | image_scanning_configuration | 이미지 취약점 스캔 |
| Encryption | encryption_configuration | 이미지 저장 암호화 |
| Lifecycle Policy | aws_ecr_lifecycle_policy | 오래된 이미지 자동 정리 |
| Repository Policy | aws_ecr_repository_policy | 다른 계정이나 특정 주체 접근 허용 |
초보자라면 먼저 다음 구성을 기본값으로 생각하면 좋다.
Private ECR Repository
scan_on_push = true
image_tag_mutability = "IMMUTABLE"
Lifecycle Policy 설정
repository_url output 생성
5. 기본 ECR Repository 만들기
가장 기본적인 ECR Repository는 다음처럼 만들 수 있다.
resource "aws_ecr_repository" "app" {
name = "${var.project_name}/${var.environment}/app"
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-app-ecr"
})
}
ECR Repository 이름은 슬래시를 포함할 수 있다.
예를 들어 다음처럼 환경과 서비스 이름을 포함해서 정리할 수 있다.
demo/dev/app
demo/prod/app
demo/prod/admin
이렇게 이름을 잡으면 여러 환경과 서비스를 구분하기 쉽다.
6. Image Tag Mutability 설정
ECR에서는 같은 태그를 다시 push해서 덮어쓸 수 있다.
예를 들어 다음 태그를 이미 push했다고 하자.
app:latest
app:v1.0.0
Tag mutability가 mutable이면 같은 태그를 다시 push할 수 있다.
app:v1.0.0
→ 다시 push
→ 기존 태그가 새 이미지로 변경될 수 있음
이 방식은 편하지만 운영에서는 위험할 수 있다.
같은 태그가 가리키는 이미지가 바뀌면, 어떤 이미지가 실제로 배포되었는지 추적하기 어려워질 수 있기 때문이다.
그래서 운영 Repository에서는 IMMUTABLE을 고려하는 것이 좋다.
resource "aws_ecr_repository" "app" {
name = "${var.project_name}/${var.environment}/app"
image_tag_mutability = "IMMUTABLE"
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-app-ecr"
})
}
이렇게 설정하면 이미 존재하는 태그로 다시 push할 때 실패한다.
이미 존재하는 tag
→ 다시 push 시도
→ 실패
개발 환경에서는 편의상 MUTABLE을 사용할 수도 있다.
하지만 운영 환경에서는 같은 태그가 다른 이미지를 가리키지 않도록 IMMUTABLE을 사용하는 편이 더 안전하다.
| 설정 | 의미 | 추천 상황 |
|---|---|---|
| MUTABLE | 같은 태그 덮어쓰기 가능 | 개발, 테스트 |
| IMMUTABLE | 같은 태그 덮어쓰기 방지 | 운영, 배포 추적 필요 |
7. Image Scanning 설정
ECR은 이미지 취약점 스캔 기능을 제공한다.
Repository 단위로 push 시점에 스캔하도록 설정할 수 있다.
resource "aws_ecr_repository" "app" {
name = "${var.project_name}/${var.environment}/app"
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-app-ecr"
})
}
scan_on_push = true를 설정하면 이미지가 push될 때 취약점 스캔이 수행된다.
docker push
→ ECR image scan
→ scan result 확인
다만 image scanning을 켰다고 해서 모든 보안 문제가 자동으로 해결되는 것은 아니다.
스캔 결과를 확인하고, 취약한 base image나 package를 업데이트하는 운영 절차가 함께 필요하다.
Image scanning은 보안 점검의 시작점이지, 보안 조치의 끝은 아니다.
8. Encryption 설정
ECR에 저장되는 이미지는 암호화되어 저장된다.
Terraform에서는 암호화 방식을 명시할 수 있다.
기본적인 AES256 암호화는 다음처럼 작성할 수 있다.
resource "aws_ecr_repository" "app" {
name = "${var.project_name}/${var.environment}/app"
encryption_configuration {
encryption_type = "AES256"
}
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-app-ecr"
})
}
KMS Key를 사용하고 싶다면 다음처럼 설정할 수 있다.
resource "aws_kms_key" "ecr" {
description = "KMS key for ECR"
deletion_window_in_days = 7
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-ecr-kms-key"
})
}
resource "aws_ecr_repository" "app" {
name = "${var.project_name}/${var.environment}/app"
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-app-ecr"
})
}
초보 단계에서는 먼저 AES256로 명시하고,
키 정책이나 감사 요구가 있는 경우 KMS를 고려해도 충분하다.
9. Lifecycle Policy 설정
ECR은 이미지를 계속 push하면 저장된 이미지가 쌓인다.
이미지가 계속 쌓이면 저장 비용이 증가하고, 오래된 이미지 관리도 어려워진다.
그래서 Lifecycle Policy를 설정하는 것이 좋다.
ECR Repository
→ 오래된 이미지 자동 정리
예를 들어 다음 정책은 untagged 이미지를 7일 후 만료시키고, 전체 이미지는 최근 20개만 남기는 예시다.
resource "aws_ecr_lifecycle_policy" "app" {
repository = aws_ecr_repository.app.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Expire untagged images older than 7 days"
selection = {
tagStatus = "untagged"
countType = "sinceImagePushed"
countUnit = "days"
countNumber = 7
}
action = {
type = "expire"
}
},
{
rulePriority = 2
description = "Keep only last 20 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 20
}
action = {
type = "expire"
}
}
]
})
}
여기서 중요한 점은 aws_ecr_lifecycle_policy는 Repository 하나에 하나만 사용하는 것이 좋다는 점이다.
여러 정리 규칙이 필요하다면 여러 개의 lifecycle policy 리소스를 만드는 것이 아니라, 하나의 policy 안에 여러 rule을 작성한다.
권장:
aws_ecr_lifecycle_policy 1개
→ rules 여러 개
비권장:
같은 repository에 aws_ecr_lifecycle_policy 여러 개
10. Repository Policy와 IAM 권한
ECR 접근 권한은 크게 두 방향에서 생각할 수 있다.
1. IAM Policy
→ 누가 ECR에 push / pull 할 수 있는가
2. Repository Policy
→ Repository 자체에서 어떤 주체를 허용할 것인가
일반적으로 같은 AWS 계정 안에서 ECS나 CI/CD가 ECR을 사용할 때는 IAM Role에 권한을 주는 방식이 많이 사용된다.
10.1 ECR Push 권한 예시
CI/CD가 Docker 이미지를 ECR에 push하려면 다음과 같은 권한이 필요하다.
resource "aws_iam_policy" "ecr_push" {
name = "${var.project_name}-${var.environment}-ecr-push"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecr:GetAuthorizationToken"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
]
Resource = aws_ecr_repository.app.arn
}
]
})
}
ecr:GetAuthorizationToken은 ECR 로그인에 필요하고,
나머지 권한은 이미지 layer 업로드와 image push에 필요하다.
10.2 ECR Pull 권한 예시
ECS Task Execution Role이나 EC2가 이미지를 pull하려면 다음 권한이 필요하다.
resource "aws_iam_policy" "ecr_pull" {
name = "${var.project_name}-${var.environment}-ecr-pull"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecr:GetAuthorizationToken"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
Resource = aws_ecr_repository.app.arn
}
]
})
}
ECS Fargate에서는 보통 Task Execution Role이 ECR pull 권한을 가진다.
ECS Task Execution Role
→ ECR image pull
→ Container 실행
10.3 Repository Policy는 언제 사용할까?
Repository Policy는 ECR Repository 자체에 붙는 정책이다.
예를 들어 다른 AWS 계정에서 이 Repository의 이미지를 pull해야 한다면 Repository Policy를 사용할 수 있다.
resource "aws_ecr_repository_policy" "allow_cross_account_pull" {
repository = aws_ecr_repository.app.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCrossAccountPull"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::123456789012:root"
}
Action = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
})
}
초보 단계에서는 먼저 같은 계정 내 IAM Role 권한 부여 방식부터 익히는 것이 좋다.
다른 계정 접근이나 조직 단위 공유가 필요할 때 Repository Policy를 고려하면 된다.
11. ECS에서 사용할 Repository URL 출력하기
ECR Repository를 만들면 ECS Task Definition에서 사용할 이미지 URL이 필요하다.
Terraform에서는 repository_url을 output으로 만들 수 있다.
output "ecr_repository_url" {
description = "ECR repository URL"
value = aws_ecr_repository.app.repository_url
}
output "ecr_repository_arn" {
description = "ECR repository ARN"
value = aws_ecr_repository.app.arn
}
output "ecr_repository_name" {
description = "ECR repository name"
value = aws_ecr_repository.app.name
}
ECS Task Definition에서는 보통 다음과 같은 형태의 이미지 URI를 사용한다.
${repository_url}:${image_tag}
예를 들어 다음과 같은 값이 된다.
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/demo/prod/app:v1.0.0
이 값은 나중에 ECS Task Definition이나 CI/CD에서 사용된다.
ECR repository_url
→ Docker tag
→ ECS image URI
12. 실전 예제: 애플리케이션용 ECR 구성
이제 애플리케이션용 ECR Repository 구성을 하나로 정리해보자.
12.1 variables.tf
variable "project_name" {
description = "Project name"
type = string
}
variable "environment" {
description = "Environment name"
type = string
}
variable "repository_name" {
description = "ECR repository name"
type = string
default = "app"
}
12.2 locals.tf
locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "terraform"
}
ecr_name = "${var.project_name}/${var.environment}/${var.repository_name}"
}
12.3 main.tf
resource "aws_ecr_repository" "app" {
name = local.ecr_name
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "AES256"
}
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-${var.repository_name}-ecr"
})
}
resource "aws_ecr_lifecycle_policy" "app" {
repository = aws_ecr_repository.app.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Expire untagged images older than 7 days"
selection = {
tagStatus = "untagged"
countType = "sinceImagePushed"
countUnit = "days"
countNumber = 7
}
action = {
type = "expire"
}
},
{
rulePriority = 2
description = "Keep only last 20 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 20
}
action = {
type = "expire"
}
}
]
})
}
12.4 outputs.tf
output "ecr_repository_url" {
description = "ECR repository URL"
value = aws_ecr_repository.app.repository_url
}
output "ecr_repository_arn" {
description = "ECR repository ARN"
value = aws_ecr_repository.app.arn
}
output "ecr_repository_name" {
description = "ECR repository name"
value = aws_ecr_repository.app.name
}
이 구성의 특징은 다음과 같다.
Repository 이름에 project / environment / service 반영
Tag immutability 활성화
Image scan on push 활성화
AES256 암호화 명시
Lifecycle Policy로 오래된 이미지 정리
ECS와 CI/CD에서 사용할 repository_url output 제공
13. 의존성 흐름
ECR을 Terraform으로 구현할 때의 의존성 흐름은 비교적 단순하다.
ECR Repository
→ Lifecycle Policy
→ IAM Policy
→ ECS / CI/CD

이 구조에서 중요한 점은 다음과 같다.
Lifecycle Policy는 ECR Repository 이름을 참조한다.
Push IAM Policy는 ECR Repository ARN을 참조한다.
Pull IAM Policy도 ECR Repository ARN을 참조한다.
ECS나 CI/CD는 IAM Role을 통해 ECR에 접근한다.
ECR Repository는 이미지를 저장하는 리소스이고, IAM Policy는 누가 push 또는 pull 할 수 있는지를 제어한다.
14. 자주 하는 실수
14.1 Terraform으로 docker push까지 하려고 함
Terraform은 인프라 리소스를 선언하고 관리하는 도구다.
Docker 이미지를 빌드하고 push하는 작업은 보통 CI/CD에서 처리하는 것이 좋다.
Terraform
→ ECR Repository 생성
CI/CD
→ docker build
→ docker push
→ ECS 배포
14.2 latest 태그만 사용함
latest 태그만 사용하면 어떤 이미지가 실제로 배포되었는지 추적하기 어렵다.
비추천:
app:latest
추천:
app:v1.0.0
app:git-sha-abc1234
app:20260514-1530
운영에서는 Git SHA, 버전 번호, 빌드 번호 같은 고유한 태그를 사용하는 것이 좋다.
14.3 운영 Repository를 MUTABLE로 둠
Tag mutability가 mutable이면 같은 태그를 다시 push해서 덮어쓸 수 있다.
운영에서는 배포 추적을 위해 IMMUTABLE을 고려하는 것이 좋다.
운영 권장:
image_tag_mutability = "IMMUTABLE"
14.4 Lifecycle Policy를 설정하지 않음
이미지를 계속 push하면 ECR에 이미지가 계속 쌓인다.
오래된 이미지를 정리하지 않으면 저장 비용이 증가하고 관리도 어려워진다.
ECR images 계속 증가
→ 저장 비용 증가
→ Lifecycle Policy 필요
14.5 untagged 이미지를 방치함
배포 과정에서 태그가 바뀌거나 이미지를 덮어쓰다 보면 untagged 이미지가 남을 수 있다.
이런 이미지는 사용 중인지 확인하기 어렵고 비용만 발생시킬 수 있다.
untagged images
→ 7일 후 삭제
Lifecycle Policy로 정리하는 것이 좋다.
14.6 ECS Task Execution Role에 ECR Pull 권한이 없음
ECS가 ECR 이미지를 가져오려면 Task Execution Role에 ECR pull 권한이 필요하다.
ECS Task Execution Role
→ ecr:GetAuthorizationToken
→ ecr:BatchGetImage
→ ecr:GetDownloadUrlForLayer
권한이 부족하면 ECS Task가 이미지를 pull하지 못하고 실행에 실패할 수 있다.
14.7 Repository URL을 output으로 만들지 않음
ECR Repository URL은 ECS Task Definition, CI/CD, Docker tag 명령에서 자주 사용된다.
따라서 output으로 만들어두면 이후 글이나 모듈에서 참조하기 좋다.
output "ecr_repository_url" {
value = aws_ecr_repository.app.repository_url
}
14.8 ECR 삭제 시 이미지가 남아 있어 실패함
ECR Repository 안에 이미지가 남아 있으면 삭제가 실패할 수 있다.
학습용에서는 삭제 전에 이미지를 비우거나, Terraform 리소스에서 force_delete 사용을 고려할 수 있다.
resource "aws_ecr_repository" "app" {
name = local.ecr_name
force_delete = true
}
다만 운영 Repository에서 force_delete = true를 사용하는 것은 위험할 수 있다.
학습용:
force_delete = true 고려 가능
운영:
신중하게 사용
15. 마무리
이번 글에서는 Terraform으로 ECR을 구현하는 방법을 정리했다.
ECR은 단순한 Docker 이미지 저장소처럼 보이지만, 실제 운영에서는 다음 설정을 함께 고려해야 한다.
Repository 이름 규칙
Tag immutability
Image scanning
Encryption
Lifecycle Policy
IAM Push / Pull 권한
repository_url output
특히 Terraform과 CI/CD의 역할을 분리해서 생각하는 것이 중요하다.
Terraform
→ ECR Repository와 정책 구성
CI/CD
→ Docker image build / push / deploy
ECR은 이후 ECS Fargate 글에서 다시 등장한다.
ECS Task Definition은 ECR의 repository_url과 image tag를 조합해서 컨테이너 이미지를 실행하게 된다.
한 줄 정리
ECR은 Docker 이미지를 저장하는 기반 리소스이고, Terraform은 저장소와 정책을 만들고 CI/CD는 이미지를 push한다.
다음 글에서는 ECS Fargate를 Terraform으로 구현해본다. ECS는 ECR에 저장된 이미지를 가져와 컨테이너를 실행하고, ALB Target Group과 연결해 실제 트래픽을 처리하는 리소스다.
'테라폼' 카테고리의 다른 글
| 4-12. 테라폼 - Lambda와 EventBridge 구현하기 (0) | 2026.05.15 |
|---|---|
| 4-11. 테라폼 - ECS Fargate 기본 구현하기 (0) | 2026.05.14 |
| 4-9. 테라폼 - ALB와 Target Group 구현하기 (0) | 2026.05.14 |
| 4-8. 테라폼 - Secrets Manager와 SSM Parameter Store 구현하기 (0) | 2026.05.13 |
| 4-7. 테라폼 - RDS MySQL 구현하기 (0) | 2026.05.13 |