ECR — 이미지 레지스트리
ECS와 Lambda가 가져갈 컨테이너 이미지를 어디에 보관하는지 정리합니다. Amazon ECR의 private / public 차이, IAM 인증, docker push / pull, 이미지 스캔, 태그 전략, 라이프사이클 정책, 멀티 아키텍처(linux/amd64 + arm64), VPC Endpoint와 크로스 계정 접근까지 다룹니다.
직전 15장 ECS와 Fargate에서 컨테이너 운영의 그림을 잡았습니다. 한 가지가 비어 있습니다 — 그 ECS / Fargate가 가져가는 이미지가 어디에 있는가입니다. Docker Hub 같은 외부 레지스트리도 가능하지만, AWS 안에서는 표준이 **Amazon ECR (Elastic Container Registry)**입니다.
본 챕터는 ECR의 구조를 한 번에 정리합니다 — private / public 차이, IAM 인증, 이미지 푸시 / 풀, 보안(스캔, 태그 불변성), 운영(라이프사이클 정책, 멀티 아키텍처)입니다. 여기서 잡는 push / pull 흐름과 라이프사이클 정책은 4부 22장 ECS Fargate 배포 골격과 24장 CI/CD 파이프라인에서 매번 등장합니다.
이미지 레지스트리의 역할 #
도커의 흐름을 떠올려 봅니다.
docker build → 로컬 이미지
↓
docker push → 레지스트리 (원격)
↓
docker pull (다른 머신) → 그 이미지를 가져와 docker run레지스트리는 그 중간 지점입니다. 누가, 어디서, 어떤 버전의 이미지를 받을 수 있는지를 결정합니다.
옵션 비교 #
| 레지스트리 | 용도 |
|---|---|
| Docker Hub | 가장 유명. 무료지만 pull 한도, public 기본 |
| GHCR (GitHub Container Registry) | GitHub 계정과 연결. private 무료 한도 큼 |
| Amazon ECR Private | AWS 안에서 IAM으로 인증. ECS / Lambda / EKS와 자연스럽게 연결 |
| Amazon ECR Public | OSS 배포용. 누구나 anonymous pull |
| GCR / Azure ACR | 다른 클라우드용 |
ECS / Lambda / EKS가 AWS 안에 있다면 ECR이 표준입니다.
- IAM으로 인증합니다 — 별도 비밀번호 관리가 없습니다.
- VPC Endpoint로 인터넷을 안 거치고 pull 합니다 (NAT 비용 절감).
- 같은 리전이라 pull이 빠릅니다.
- 이미지 스캔 (취약점 자동 분석)이 통합됩니다.
Private vs Public #
ECR은 두 종류입니다.
Private (대부분의 경우) #
내 계정의 사용자 / 역할만 접근할 수 있습니다. 회사 / 운영 워크로드는 거의 다 여기입니다.
- 리전 단위 (이미지마다 리전이 박힙니다)
- IAM 정책으로 접근 제어
- 비용: GB 저장 + Data Transfer
Public (OSS 배포 / 학습 자료) #
전 세계 누구나 anonymous pull 가능합니다. AWS가 운영하는 Public Gallery에 노출됩니다.
- 항상
us-east-1에 호스팅 (글로벌) - Push는 IAM 인증, Pull은 무인증
- 비용: GB 저장 + Data Transfer (push 측)
본 챕터는 Private 기준으로 진행합니다.
Repository 만들기 #
ECR의 단위는 Repository입니다. 하나의 repo 안에 같은 앱의 여러 버전 (태그)을 보관합니다.
aws ecr create-repository \
--repository-name myapp \
--region ap-northeast-2 \
--image-scanning-configuration scanOnPush=true \
--encryption-configuration encryptionType=AES256성공 시 URI입니다.
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp이것이 모든 push / pull의 주소입니다. 형태는 다음과 같습니다.
<계정ID>.dkr.ecr.<리전>.amazonaws.com/<repo>:<태그>옵션 정리 #
| 옵션 | 의미 |
|---|---|
image-scanning-configuration scanOnPush=true | push 시 자동 취약점 스캔 |
image-tag-mutability IMMUTABLE | 같은 태그 덮어쓰기 금지 — 운영 권장 |
encryption-configuration encryptionType=KMS | 사용자 관리 KMS 키로 암호화 |
콘솔에서 GUI로도 동일하게 만들 수 있습니다.
인증 — aws ecr get-login-password
#
Docker Hub와 달리 ECR은 AWS IAM으로 인증합니다. 비밀번호가 따로 없습니다. 대신 임시 토큰을 받아 docker login 합니다.
aws ecr get-login-password --region ap-northeast-2 \
| docker login --username AWS --password-stdin \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com토큰은 12시간 유효합니다. CI에서는 매번 새로 받아 쓰는 것이 자연스럽습니다 (CI 잡 시작 시 한 번).
필요한 권한 #
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:BatchGetImage"
],
"Resource": "arn:aws:ecr:ap-northeast-2:123456789012:repository/myapp"
}
]
}GetAuthorizationToken만 * 자원이고, 나머지는 특정 repo로 제한합니다 (6장 보안 기본의 최소 권한).
Pull만 하는 권한 #
ECS Task의 Execution Role처럼 pull만 하면 됩니다. AWS 매니지드 정책 **AmazonECSTaskExecutionRolePolicy**가 ECR pull 권한을 자동 포함합니다.
Push / Pull #
Push #
# 빌드
docker build -t myapp:v1 .
# 태그 (ECR URI로)
docker tag myapp:v1 \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1
# 로그인 (위 참고)
aws ecr get-login-password --region ap-northeast-2 | docker login ...
# push
docker push \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1이미지가 100MB 면 첫 push는 100MB 업로드입니다. 다음부터는 레이어 단위 캐싱이라 변경된 부분만 올라가서 보통 수 MB입니다.
Pull #
docker pull 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1ECS / Lambda는 자동으로 pull 합니다. 콘솔에서 직접 할 일은 거의 없지만 디버깅 시 유용합니다.
태그 전략 #
ECR repo 안에 같은 이미지의 여러 버전을 어떻게 부를지에 관한 규칙입니다. 자주 보는 패턴입니다.
1) Semver #
myapp:1.4.2
myapp:1.4
myapp:1
myapp:latest라이브러리 / 도구처럼 외부에 배포하는 용도에 자연스럽습니다. 운영에서는 latest가 위험합니다 (어느 시점의 latest인지 모호).
2) Git SHA #
myapp:abc1234 ← short sha
myapp:abc1234567... ← full shaCI에서 빌드한 commit과 1:1 매칭됩니다. 운영에 가장 추천하는 방식입니다 — 어느 commit이 운영에 있는지 즉시 추적할 수 있습니다.
3) 환경 + 시퀀스 #
myapp:prod-2025-04-01.001
myapp:staging-2025-04-01.005릴리스를 날짜별로 세는 용도에서 씁니다.
4) 멀티 태그 #
운영 권장 패턴은 immutable + alias입니다.
docker tag myapp:abc1234 myapp:abc1234 # 불변 (영원히 그대로)
docker tag myapp:abc1234 myapp:prod-current # 가변 (현재 운영 가리킴)ECR repo 자체는 IMMUTABLE (한 번 push 한 태그를 못 덮어씀)로 두고, alias가 필요하면 별도 도구 (deployment system)가 관리합니다.
이미지 스캔 #
ECR은 push 한 이미지의 취약점을 자동 스캔해 줍니다. scanOnPush=true 옵션입니다 (위에서 설정).
두 종류 #
| 종류 | 무엇 | 비용 |
|---|---|---|
| Basic Scanning | open source CVE DB (CoreOS Clair) 기반 단발 스캔 | 무료 |
| Enhanced Scanning | Inspector 통합. OS 레이어 + 언어 라이브러리 (npm, pip 등)까지. 지속 모니터링 (이미지 push 후에도 새 CVE 발견 시 알림) | repo 당 시간 / 이미지당 |
운영 워크로드는 Enhanced를 검토합니다. Basic도 첫 출발에는 충분합니다.
결과 보기 #
aws ecr describe-image-scan-findings \
--repository-name myapp \
--image-id imageTag=v1콘솔에서는 repo → 이미지 → “Vulnerabilities” 탭에서 CRITICAL / HIGH / MEDIUM / LOW 수가 한눈에 보입니다.
빌드 단에서 차단하기 #
CRITICAL이 있으면 배포를 막습니다 — CI 잡에서 다음을 둡니다.
CRITICAL=$(aws ecr describe-image-scan-findings \
--repository-name myapp --image-id imageTag=$SHA \
--query 'imageScanFindings.findingSeverityCounts.CRITICAL' \
--output text)
if [ "$CRITICAL" != "None" ] && [ "$CRITICAL" -gt 0 ]; then
echo "🚨 CRITICAL CVE 발견. 배포 중단."
exit 1
fi이 게이트는 24장 CI/CD 파이프라인에서 빌드 단계에 그대로 끼워 넣습니다.
라이프사이클 정책 — 자동 정리 #
이미지가 쌓이면 ECR 비용이 옵니다. 라이프사이클 정책으로 자동 정리합니다.
{
"rules": [
{
"rulePriority": 1,
"description": "untagged 이미지 7일 후 삭제",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "최신 30개만 유지 (그 외 삭제)",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 30
},
"action": { "type": "expire" }
}
]
}aws ecr put-lifecycle-policy \
--repository-name myapp \
--lifecycle-policy-text file://lifecycle.json흔한 패턴은 다음과 같습니다.
- untagged 7~14일 후 삭제 (실패한 빌드의 잔재)
- 태그 prefix
pr-인 것 30일 후 삭제 (PR 미리보기 이미지) - 태그 prefix
release-는 영구 보존
운영 워크로드는 1 년치 이미지가 누적되면 GB 단위 비용이 듭니다. 라이프사이클을 처음에 잡아 두는 것이 운영 위생의 핵심입니다.
멀티 아키텍처 이미지 #
Apple Silicon Mac (arm64)에서 빌드한 이미지를 그대로 운영 (보통 amd64)에 push 하면 부팅이 안 됩니다. 두 길이 있습니다.
1) buildx로 다중 플랫폼 push #
docker buildx create --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1 \
--push .ECR 한 태그 (v1) 안에 두 아키텍처가 함께 들어가는 manifest list입니다. pull 측이 자기 아키텍처에 맞는 것을 자동 선택합니다.
2) Fargate ARM으로 통일 #
15장 Fargate에서 task definition의 runtimePlatform.cpuArchitecture: ARM64로 두면 ARM 전용 이미지만 push 해도 됩니다. 단가가 약 20% 저렴한 보너스가 있습니다.
{
"runtimePlatform": {
"cpuArchitecture": "ARM64",
"operatingSystemFamily": "LINUX"
}
}작은 / 중간 트래픽의 새 프로젝트는 처음부터 ARM을 권장합니다.
VPC Endpoint — NAT 없이 pull #
private subnet의 ECS Task가 ECR에서 pull 하면 기본은 NAT Gateway 경유라 GB 당 과금됩니다.
ECR은 VPC Endpoint를 지원해 NAT를 우회합니다.
# api 호출용
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxx \
--service-name com.amazonaws.ap-northeast-2.ecr.api \
--vpc-endpoint-type Interface \
--subnet-ids subnet-aaa subnet-bbb
# 이미지 layer 다운로드용
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxx \
--service-name com.amazonaws.ap-northeast-2.ecr.dkr \
--vpc-endpoint-type Interface \
--subnet-ids subnet-aaa subnet-bbb
# 이미지 layer가 S3에 살아 있어 S3 endpoint도 같이
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxx \
--service-name com.amazonaws.ap-northeast-2.s3 \
--vpc-endpoint-type Gateway \
--route-table-ids rtb-xxx세 개 (api, dkr, s3)가 한 세트입니다. NAT Gateway 비용을 크게 줄여 줍니다 — 운영 트래픽이 큰 환경에서는 거의 필수입니다. VPC와 endpoint의 깊이는 28장 VPC 깊이에서 더 다룹니다.
크로스 계정 접근 #
prod 계정의 ECR repo를 dev 계정에서 pull 하고 싶다면 Repository Policy로 허용합니다.
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowDevAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
]
}
]
}aws ecr set-repository-policy \
--repository-name myapp \
--policy-text file://repo-policy.json원칙은 다음과 같습니다. 운영 이미지는 운영 계정의 ECR에 한 번만 두고 다른 계정은 pull 합니다. 같은 이미지를 환경별 계정에 중복 빌드하지 않습니다. 멀티 어카운트 거버넌스는 29장 보안 거버넌스에서 다룹니다.
비용 #
| 항목 | 가격 (서울 리전) |
|---|---|
| Storage | GB / 월 $0.10 |
| Data Transfer Out (인터넷) | GB $0.126 (1GB 무료) |
| Data Transfer Out (같은 리전) | 무료 |
| Enhanced Scanning | 이미지당 + repo 당 시간 |
같은 리전 안의 ECS가 pull 하면 무료입니다. 인터넷으로 나가는 (CI / 외부 도구) pull만 비용입니다. 라이프사이클 정책으로 storage만 잡으면 비용은 거의 0에 가깝습니다.
자주 만나는 함정 #
1) denied: ... is not authorized to perform: ecr:...
#
권한 누락입니다. 둘 다 확인합니다.
- 사용자 / 역할 정책에 ECR action이 들어 있는지
- Repository Policy가 차단하지 않는지 (보통 비어 있음 — 비어 있으면 IAM 정책만으로 충분)
2) manifest unknown 또는 repository ... not found
#
99%는 리전 / 계정 ID 오타입니다. URI의 ap-northeast-2 위치, 123456789012 위치를 다시 확인합니다.
3) IMMUTABLE repo에 같은 태그 push
#
같은 태그를 두 번 push 하려고 하면 거부됩니다. 의도된 동작이며 운영 권장입니다. CI 잡에서 commit SHA로 태그 짓는 식으로 우회합니다.
4) 멀티 아키텍처 빠뜨림 #
Mac (ARM)에서 빌드 → push → x86_64 Fargate에서 exec format error가 납니다. buildx로 multi-arch 하거나 task definition을 ARM으로 통일합니다.
5) NAT Gateway 비용 폭발 #
ECR pull이 NAT를 거치면 GB 당 과금됩니다. VPC Endpoint 셋 (api / dkr / s3)을 추가합니다.
6) 이미지 누적 #
라이프사이클 정책 없이 1년 운영하면 수천 개의 이미지가 GB 단위 비용을 만듭니다. 첫 repo 만들 때 라이프사이클도 같이 둡니다.
연습문제 #
- 본인의 앱 이미지에 어떤 태그 전략을 쓸지 §“태그 전략"의 네 패턴 중 하나를 고르고, 운영에서
latest를 쓰지 않는 이유를 한 줄로 적어 두세요. 24장 CI/CD 파이프라인에서 CI가 commit SHA로 태그를 붙일 때 이 결정이 기준이 됩니다. - private subnet의 ECS Task가 ECR에서 pull 할 때 NAT Gateway를 거치는 경로와 VPC Endpoint(api / dkr / s3)를 거치는 경로의 비용 차이를 §“VPC Endpoint"와 §“비용"을 근거로 한 단락으로 설명해 보세요(15장 ECS와 Fargate의 NAT 비용 함정과 묶어 봅니다).
- 라이프사이클 정책에서 untagged 이미지 7일 삭제 + 최신 30개 유지 룰을 적용하면, 1년 동안 매일 한 번씩 배포할 때 repo에 남는 이미지 수가 대략 얼마가 되는지 §“라이프사이클 정책"을 근거로 추정해 보세요.
한 줄 요약: ECR은 AWS 안의 컨테이너 이미지 레지스트리로, ECS / Lambda / EKS와 IAM으로 연결되며 운영은 Private repo를 쓴다. 인증은 12시간짜리 토큰을 받는
aws ecr get-login-password이고, 운영 권장 태그는 Git SHA + IMMUTABLE이다. push 시 이미지 스캔으로 CRITICAL을 CI에서 차단하고, 라이프사이클 정책으로 이미지를 자동 정리한다. 멀티 아키텍처는 buildx 또는 Fargate ARM으로 해결하고, VPC Endpoint(api·dkr·s3)로 NAT 비용을 회피한다.
다음 챕터 #
ECS와 ECR은 컨테이너가 항상 떠 있는 모델입니다. 다음 17장 Lambda 기초에서는 반대편 — 요청이 올 때만 함수가 깨어나는 서버리스 — 를 다룹니다. Lambda의 동작 방식, runtime / handler / 이벤트 모델, 콜드 스타트, 동시성, 로깅까지 AWS 서버리스의 첫 단추를 정리합니다.