GitHub Actions + Docker Buildx로 레이어 캐시 적용하기 – CI 빌드 시간 70% 단축하기

2025. 11. 26. 20:24Infra

Docker 레이어 캐시를 GitHub Actions에서 Buildx와 ECR를 활용해 적용한 과정을 공유하려고 합니다. 특히, 의존성 설치 단계를 분리해 캐시 히트율을 극대화하고, 빌드 시간을 70% 이상 단축한 사례를 소개합니다.

 

Docker 레이어 캐시가 필요한가?

Docker는 레이어 기반으로 이미지를 빌드합니다. RUN, COPY, ADD 등의 명령어는 각각 하나의 레이어를 생성하고, 이 레이어가 변경되지 않으면 재사용(캐시)됩니다.

하지만 CI 환경에서는: • 매번 새로운 runner 사용 → 로컬 캐시 없음 • requirements.txt가 자주 변경되지 않는데도 COPY ./app /app 후 pip install이 매번 실행됨 • 결과적으로 의존성 설치에 3~5분 소요

이걸 해결하기 위해 Docker Buildx의 레지스트리 기반 캐시를 도입했습니다.


1. GitHub Actions에 Buildx 설정 추가

name: Deploy to DEV Amazon ECR and EKS

on:
  push:
    branches:
      - dev
      
env:
  AWS_REGION: ap-northeast-2
  ECR_REPOSITORY: test


jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    
    permissions:
      id-token: write
      contents: read
	
    steps:
	# buildx 설정 추가
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Build, tag, and push image
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ steps.tag.outputs.date }}
      run: {}

2. 기존 빌드 스텝 → Buildx + 캐시 적용

# 변경 전 (기존 코드)
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f docker/Dockerfile .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

# 변경 후 (Buildx + 레지스트리 캐시)
BRANCH_NAME=$(echo "${{ github.ref_name }}" | sed 's|/|-|g' | sed 's/[^a-zA-Z0-9_.-]//g')
CACHE_TAG="cache-$BRANCH_NAME"

docker buildx build \
  --cache-from type=registry,ref=$ECR_REGISTRY/$ECR_REPOSITORY:$CACHE_TAG \
  --cache-to type=registry,ref=$ECR_REGISTRY/$ECR_REPOSITORY:$CACHE_TAG,mode=max \
  -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
  -f docker/Dockerfile \
  --push .
  • --cache-from : ECR에 저장된 캐시 레이어를 가져와 재사용
  • --cache-to : 빌드 후 캐시를 ECR에 저장 (mode=max → 모든 레이어 저장)
  • CACHE_TAG : 브랜치별 캐시 분리 (cache-main, cache-feature-x)
    • 브랜치별 캐시 분리로 main 브랜치의 변경이 feature 브랜치 캐시에 영향을 주지 않도록 처리

ECR 캐시 이미지 생성 확인


3. Dockerfile 최적화 – 의존성 설치 레이어 분리

Dockerfile의 레이어 순서는 캐시 효율을 결정합니다. 자주 변경되는 코드는 가장 아래에, 드물게 변경되는 의존성은 위에 배치해야 합니다.

기존 DockerFile

FROM python:3.12.4

ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Seoul

RUN apt-get update \
&& apt-get --no-install-recommends install -yq tzdata nano cron postgresql-client \
&& apt-get autoremove \
&& apt-get clean \

COPY ./app /app
RUN pip install --no-cache-dir -r /app/requirements.txt

## 불필요한 suid, guid 제거, cronjob 관련 이관
...생략


USER appuser

WORKDIR /app
EXPOSE 8000

캐시 hit를 높이기 위한 DockerFile

  •  app/ 디렉토리 내 어떤 파일이라도 변경되면 pip install 레이어가 무효화됨
FROM python:3.12.4

ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Seoul

RUN apt-get update \
&& apt-get --no-install-recommends install -yq tzdata nano cron postgresql-client \
&& apt-get autoremove \
&& apt-get clean

# 변경 점이 적은 requirements install 부분을 앞에서 처리
COPY app/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt

## 불필요한 suid, guid 제거, cronjob 관련 이관
...생략

# 변경사항이 많은 app copy는 후처리
COPY ./app /app

USER appuser

WORKDIR /app
EXPOSE 8000

 

  1. requirements.txt만 먼저 복사
  2. pip install 실행 → 이 레이어는 requirements.txt가 변하지 않으면 캐시 재사용
  3. 나중에 전체 app/ 복사 → 코드 변경 시에도 의존성 설치 스킵

빌드 시간 비교

조건 빌드 시간 비고
변경 전 3분 10초~ 4분 10초  의존성 매번 설치
변경 후 40초~ 1분 40초 캐시 히트 시
감소율 60% ~ 78%  


주의사항 및 팁

  1. 캐시 태그 충돌 방지 → cache-$BRANCH_NAME으로 브랜치별 분리
  2. mode=max 사용 시 용량 주의 → 모든 레이어 저장 → ECR 비용 증가 가능 → 필요 시 mode=min 또는 주기적 삭제 정책 고려
  3. ECR 권한 설정 → GitHub Actions의 IAM 역할에 ecr:BatchGetImage, ecr:BatchCheckLayerAvailability, ecr:PutImage 권한 필요

마무리

Docker 레이어 캐시는 CI/CD 파이프라인 속도를 결정짓는 핵심 요소입니다. 특히 Python 프로젝트처럼 의존성 설치가 무거운 경우, 레이어 순서 조정과 Buildx 캐시는 필수입니다.

GitHub Actions에서 Docker 빌드 속도가 느리다면 고려해보는게 좋습니다.