ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Amazon ECR로 Traefik·WordPress 업데이트하기 — 이미지를 어디서 당겨올지 내가 정한다는 것
    IT 2026. 9. 19. 21:00

    ▶ 동영상 개요 — Amazon ECR로 Traefik·WordPress 업데이트하기 — 이미지를 어디서 당겨올지 내가 정한다는 것

    8분 3초 — 로컬 Traefik·WordPress 스택을 업데이트할 때 Docker Hub에서 직접 latest를 당기지 않고 Amazon ECR pull-through 캐… — NotebookLM 동영상 개요로 생성

    🎧 오디오 개요 — Amazon ECR로 Traefik·WordPress 업데이트하기 — 이미지를 어디서 당겨올지 내가 정한다는 것

    21분 59초 — 로컬 Traefik·WordPress 스택을 업데이트할 때 Docker Hub에서 직접 latest를 당기지 않고 Amazon ECR pull-through 캐…. 화면은 이 글의 인포그래픽 한 장 고정 — NotebookLM 오디오 개요로 생성

    로컬에 Traefik과 WordPress를 docker compose로 띄워 두고 잘 쓰고 있었다. image: wordpress:6.6, image: traefik:v3.1처럼 Docker Hub에서 곧장 당겨오는 흔한 구성이다. 그런데 어느 날 docker compose pull을 돌렸더니 toomanyrequests: You have reached your pull rate limit이 떴다. Docker Hub는 로그인 안 한 익명 사용자에게 6시간당 100회만 이미지 내려받기를 허용한다. 집·회사·CI가 같은 공인 IP를 쓰면 그 100회는 생각보다 금방 소진된다.

    더 불편한 건 따로 있었다. wordpress:6.6이라는 태그는 같은 이름인데 내용이 계속 바뀐다. 어제 당긴 6.6과 오늘 당긴 6.6이 다른 이미지일 수 있다는 뜻이다. "업데이트를 눌렀더니 멀쩡하던 사이트가 깨졌다"의 절반은 여기서 온다. 그래서 이번 글의 주인공을 부른다 — Amazon ECR(Elastic Container Registry)이다. AWS가 운영하는 사설 컨테이너 이미지 저장소로, 쉽게 말하면 "내 계정 전용 Docker Hub"다. 이 글은 ECR이 무엇을 하는 물건인지, 그리고 그것을 Traefik·WordPress 업데이트에 어떻게 끼워 넣는지 실제 명령까지 따라가며 정리한 기록이다.

    ECR이 정확히 무엇인가 — 이미지를 보관하는 사설 창고

    먼저 용어부터 한 줄로 푼다. 컨테이너 이미지(container image)는 애플리케이션과 그 실행 환경을 통째로 봉인해 놓은 파일 묶음이다. WordPress면 PHP 런타임·확장·워드프레스 코드가 한 덩어리로 들어 있다. 레지스트리(registry)는 그 이미지들을 버전별로 쌓아 두고 올리고(push) 내려받는(pull) 창고다. Docker Hub가 가장 유명한 공개 레지스트리이고, Amazon ECR은 AWS가 관리해 주는 사설 레지스트리다. "사설"이 핵심이다 — 기본적으로 내 AWS 계정 안에서 권한을 가진 사람·서버만 접근한다.

    ECR 저장소 주소는 <계정ID>.dkr.ecr.<리전>.amazonaws.com/<저장소이름> 형태다. 예를 들어 서울 리전에 만든 WordPress 저장소라면 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-wordpress가 된다. Docker Hub의 wordpress:6.6이 짧은 이유는 "생략된 앞부분이 곧 Docker Hub"라는 암묵적 기본값 때문이고, ECR은 이 주소를 명시적으로 다 적는다. 즉 이미지를 어디서 당겨올지가 주소에 그대로 박힌다.

    전체 지도 — 업데이트 경로에서 ECR이 앉는 자리

    diagram

    다이어그램 설명. 이 그림이 보여주는 건 ECR을 "Docker Hub와 내 실행 스택 사이에 끼우는 중간 창고"로 쓴다는 발상이다. 원래는 실행 스택이 Docker Hub에서 직접 이미지를 당겼는데, 이제는 그 사이에 ECR이 들어가 실제 pull은 전부 ECR에서 일어난다. Docker Hub 원본은 ECR이 처음 한 번, 그리고 주기적으로만 접촉한다. 이렇게 하면 업데이트할 때 "무엇을 당길지"의 결정 지점이 Docker Hub가 아니라 내가 통제하는 ECR로 옮겨온다. 놓치기 쉬운 오해 하나를 미리 짚으면, ECR은 이미지를 보관할 뿐 컨테이너를 실행하지는 않는다 — 실행은 여전히 내 서버의 docker compose가 한다.

    ECR의 역할 — 창고가 하는 다섯 가지 일

    ECR은 그냥 파일을 쌓아 두는 곳이 아니라, 이미지를 운영하는 데 필요한 기능을 붙여 둔 창고다. 업데이트 시나리오와 직접 연결되는 것만 골라 본다.

    첫째, 사설 저장 + 접근 통제다. ECR은 IAM(Identity and Access Management, AWS의 사용자·권한 관리 체계)으로 누가 이미지를 올리고 내릴 수 있는지 정한다. 커스텀 WordPress 이미지처럼 남에게 보이면 곤란한 것을 두는 자리다. 둘째, 이미지 스캔이다. 이미지를 push하면 그 안에 알려진 취약점(CVE)이 있는지 자동으로 검사해 결과를 보여준다. 셋째, 수명주기 정책(lifecycle policy)이다. 오래된 태그나 태그 없는 이미지를 규칙으로 자동 삭제해 저장 비용을 통제한다 — 업데이트를 반복하면 옛 버전이 무한정 쌓이는데, 이걸 자동으로 청소한다. 넷째, 태그 불변성(tag immutability)이다. 한 번 올린 태그를 덮어쓰지 못하게 잠글 수 있다. 앞에서 본 "같은 6.6인데 내용이 바뀌는" 문제를 원천에서 막는다. 다섯째, pull-through 캐시다. 이게 이번 업데이트 시나리오의 실질적 주역이라 절을 따로 뗀다.

    핵심 사례 — Docker Hub를 직접 당기지 않고 ECR을 거친다

    Traefik·WordPress처럼 공개 이미지를 그대로 쓰는 경우, ECR을 끼우는 가장 깔끔한 방법이 pull-through 캐시다. 뜻은 이름 그대로다 — 내가 ECR에 이미지를 달라고 하면, ECR이 아직 없을 때만 Docker Hub 원본까지 뚫고(through) 가서 당겨온 뒤 자기 창고에 복사(cache)해 둔다. 다음부터는 Docker Hub를 건드리지 않고 ECR 안에서 응답한다. 두 방식의 차이를 그림으로 나눠 본다.

    diagram

    ▲ 방식 A — Docker Hub 직접 당기기

    다이어그램 설명. 이 방식은 실행 스택을 올리거나 업데이트할 때마다 매번 Docker Hub 원본을 두드린다. 구성이 단순한 대신, 같은 공인 IP를 여러 대가 공유하면 6시간당 100회 제한을 다 함께 나눠 쓴다. 한창 배포하다가 toomanyrequests로 막히는 게 바로 이 그림에서 벌어지는 일이다.

    diagram

    ▲ 방식 B — ECR pull-through 캐시 경유

    다이어그램 설명. 이 방식에서 실행 스택은 오직 ECR하고만 대화한다. ECR은 요청받은 이미지가 이미 자기 창고에 있으면 그대로 주고, 없을 때만 Docker Hub까지 다녀온다(점선). 한 번 캐시된 뒤로는 Docker Hub 제한과 무관하게 몇 번이든 당길 수 있다. 다만 "당긴 뒤로 원본이 바뀌면?"이 궁금할 텐데, ECR은 latest·6.6 같은 움직이는 태그를 당길 때 최소 24시간에 한 번 원본에 새 버전이 있는지 확인한다. 즉시 최신을 받아야 하면 캐시된 이미지를 지워 강제로 다시 당기게 만들면 된다.

    설정은 규칙 하나면 끝난다. Docker Hub를 대상으로 pull-through 규칙을 만든다.

    # 1단계: Docker Hub를 원본으로 하는 pull-through 캐시 규칙 등록
    #   ecr-public 는 접두어(prefix) — ECR 안에서 이 원본을 부를 별명이다
    aws ecr create-pull-through-cache-rule \
        --ecr-repository-prefix dockerhub \
        --upstream-registry-url registry-1.docker.io \
        --region ap-northeast-2
    
    # 2단계: 이제 docker-compose.yml 의 이미지 주소를
    #   Docker Hub 짧은 이름 대신 ECR 경유 주소로 바꾼다
    #   before:  image: wordpress:6.6
    #   after :  image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/dockerhub/library/wordpress:6.6
    

    코드 설명. 첫 명령은 "dockerhub라는 접두어로 들어오는 요청은 Docker Hub에서 당겨 캐시하라"는 규칙을 ECR에 심는 일이다. 그다음 할 일은 docker-compose.yml에서 image: 줄의 주소만 ECR 경유 주소로 바꾸는 것이다 — 컨테이너 실행 방식이나 Traefik 라우팅 설정은 하나도 건드리지 않는다. 공식 이미지는 Docker Hub에서 library/ 네임스페이스에 있으므로 주소에 dockerhub/library/wordpress처럼 그 경로가 그대로 붙는 게 핵심 함정이다. 이 한 줄만 바꾸면 그 뒤의 모든 pull이 자동으로 ECR을 거친다.

    실제 업데이트 절차 — login, pull, up, 그리고 롤백

    주소를 바꿨으면 이제 ECR에서 당길 수 있게 로그인부터 해야 한다. 여기에 한 가지 함정이 있다.

    # ECR 로그인 — 인증 토큰을 받아 docker 에 넘긴다
    aws ecr get-login-password --region ap-northeast-2 \
        | docker login --username AWS --password-stdin \
          123456789012.dkr.ecr.ap-northeast-2.amazonaws.com
    
    # 이미지 새 버전 받아서 스택 갱신
    docker compose pull        # ECR 경유로 최신 태그 당기기
    docker compose up -d       # 바뀐 이미지로 컨테이너만 재생성
    

    코드 설명. get-login-password가 인증 토큰을 만들어 docker login에 파이프로 넘긴다. 함정은 이 토큰의 수명이 12시간이라는 점이다. AWS 자격증명이 멀쩡해도 docker 로그인은 별도로 만료되므로, 12시간이 지나면 no basic auth credentials 같은 오류를 만난다. 그래서 자동화된 서버에서는 매번 손으로 로그인하는 대신 ECR Docker Credential Helper(자격증명 도우미)를 깔아 두는 게 정석이다 — docker가 ECR 주소로 pull을 시도할 때마다 토큰을 알아서 갱신해 준다. up -d는 이미지가 바뀐 컨테이너만 골라 재생성하므로, WordPress 데이터가 든 볼륨은 그대로 유지된다.

    이 구성의 진짜 이득은 롤백이 주소 한 줄로 끝난다는 데서 드러난다. wordpress:6.6으로 올렸다가 문제가 생기면 docker-compose.yml을 wordpress:6.5로 되돌리고 up -d만 다시 하면 된다. ECR에는 이전 버전 이미지가 그대로 캐시돼 있어 Docker Hub 사정과 무관하게 즉시 이전 상태로 돌아간다. "움직이는 태그"가 아니라 버전을 콕 집은 고정 태그를 쓰는 습관이 여기서 값을 한다 — latest로만 돌렸다면 "직전이 대체 무엇이었는지"를 알 길이 없다.

    한 걸음 더 — 커스텀 WordPress 이미지를 ECR에 올리기

    여기까지는 남이 만든 공개 이미지를 당겨오는 이야기였다. ECR의 또 다른 쓰임은 내가 만든 이미지를 올려 두는 것이다. 플러그인·테마·PHP 설정을 미리 구운 WordPress 이미지를 만들어 두면, 서버에서는 그걸 당겨 실행만 하면 된다. "새 서버에서 플러그인을 하나씩 다시 설치"하는 일이 사라진다.

    diagram

    다이어그램 설명. 이 흐름은 "한 번 구워서 여러 곳에서 그대로 쓴다"는 컨테이너의 기본 발상을 그대로 따른다. Dockerfile에 필요한 플러그인·테마·설정을 적어 이미지로 굽고(build), 그것을 ECR 사설 저장소에 올린 뒤(push), 각 서버는 그 이미지를 당겨(pull) 실행한다. 공개 이미지를 캐시만 하던 앞 절과 달리 이건 내 자산을 사설 저장소에 두는 경우라, 앞서 말한 IAM 접근 통제와 이미지 스캔이 그대로 값을 한다. 개발 노트북에서 굽든 서버에서 당기든 완전히 같은 비트의 이미지가 도는 게 이 방식의 핵심 이점이다.

    # 커스텀 이미지 굽고 ECR 사설 저장소에 올리기
    docker build -t my-wordpress:v1 .
    
    # ECR 저장소 주소로 태그를 붙인 뒤 push
    docker tag my-wordpress:v1 \
        123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-wordpress:v1
    docker push \
        123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-wordpress:v1
    

    코드 설명. build로 로컬에 이미지를 만든 뒤, docker tag로 ECR 저장소의 전체 주소를 이름표로 다시 붙이는 단계가 반드시 필요하다. push는 이미지 이름에 적힌 주소를 보고 목적지를 정하기 때문이다. 이 이미지에 태그 불변성을 걸어 두면 v1은 영원히 그 내용으로 고정되고, 다음 업데이트는 v2로 올린다. 그러면 docker-compose.yml의 태그 숫자 하나가 곧 "지금 돌고 있는 정확한 버전"의 단일 기록이 된다.

    여기에 수명주기 정책을 얹으면 옛 버전 청소도 자동이다.

    {
      "rules": [
        {
          "rulePriority": 1,
          "description": "최근 태그 이미지 10개만 남기고 나머지 삭제",
          "selection": {
            "tagStatus": "any",
            "countType": "imageCountMoreThan",
            "countNumber": 10
          },
          "action": { "type": "expire" }
        }
      ]
    }
    

    코드 설명. 이 정책은 저장소에 이미지가 10개를 넘으면 오래된 것부터 지운다. 업데이트를 반복하면 v1부터 수십 개가 쌓이는데, 롤백에 필요한 최근 몇 개만 남기고 자동으로 정리하라는 규칙이다. 정책은 하루 한 번 정도 평가되므로 즉시 지워지지는 않지만, 방치하면 조용히 늘어나는 저장 비용을 손 안 대고 묶어 둔다는 게 요점이다.

    정리 — 업데이트는 결국 "출처를 통제하는 일"이다

    Traefik·WordPress를 업데이트한다는 건 표면적으로는 "새 이미지를 받아 컨테이너를 다시 띄우는 일"이다. 하지만 한 번 겪어 보면 진짜 어려움은 "무엇을, 언제, 어디서 당겨올지"가 내 손을 떠나 있을 때 생긴다는 걸 알게 된다. Docker Hub에서 latest를 직접 당기는 구성은 그 세 가지가 전부 남의 사정에 달려 있다 — 남이 태그 내용을 바꾸면 내 사이트가 바뀌고, 남이 제한을 걸면 내 배포가 멈춘다.

    ECR을 끼우는 일은 그 결정 지점을 내 계정 안으로 가져오는 것이다. pull-through 캐시로 Docker Hub 제한과 태그 변동에서 벗어나고, 태그 불변성과 고정 버전으로 "지금 도는 것이 정확히 무엇인지"를 기록으로 남기고, 커스텀 이미지로 "한 번 구워 여러 곳에서 그대로"를 실현한다. 업데이트를 안정적으로 만드는 첫걸음은 더 빠른 배포 명령이 아니라, 이미지의 출처를 내가 쥐는 것이다. 로컬 스택 하나를 운영하더라도, 이 감각을 먼저 익혀 두면 나중에 서버가 여러 대로 늘어날 때 고칠 게 거의 없다.


    참고한 공개 자료:


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.