빌드 캐시 사용하기

빌드 캐시 사용하기

빌드 캐시를 잘 활용하면 이전 빌드의 결과를 재사용하고 불필요한 작업을 건너뛰어 빌드를 더 빠르고 효율적으로 만들 수 있어요. 이 가이드에서는 캐시 무효화가 어떻게 동작하는지 이해하고, Node.js 애플리케이션에서 빌드 캐시를 효과적으로 사용하는 법을 배워요.

출처: 문서

본문

설명

getting-started 앱을 위해 만든 다음 Dockerfile을 살펴볼게요.

FROM node:22-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "./src/index.js"]

docker build 명령으로 새 이미지를 만들면 Docker는 Dockerfile의 각 지시문을 순서대로 실행하며, 각 명령마다 레이어를 만들어요. 각 지시문에 대해 Docker는 이전 빌드의 지시문을 재사용할 수 있는지 확인해요. 이전에 비슷한 지시문을 실행한 적이 있으면 다시 실행할 필요 없이 캐시된 결과를 사용해요. 이렇게 하면 빌드 과정이 더 빠르고 효율적이 되어 귀중한 시간과 리소스를 절약할 수 있어요.

빌드 캐시를 효과적으로 사용하면 이전 빌드의 결과를 재사용하고 불필요한 작업을 건너뛰어 더 빠른 빌드를 할 수 있어요.

캐시 사용을 최대화하고 리소스 집약적이고 시간이 오래 걸리는 재빌드를 피하려면 캐시 무효화(cache invalidation)가 어떻게 동작하는지 이해하는 게 중요해요. 캐시가 무효화될 수 있는 상황의 몇 가지 예는 다음과 같아요.

  • RUN 지시문의 명령이 변경되면 그 레이어가 무효화돼요. Docker는 변경을 감지하고, Dockerfile의 RUN 명령에 수정이 있으면 빌드 캐시를 무효화해요.
  • COPYADD 지시문으로 이미지에 복사되는 파일에 변경이 생기면 무효화돼요. Docker는 프로젝트 디렉터리 안의 파일 변경을 계속 감시해요. 내용이나 권한 같은 속성의 변경이든, Docker는 이런 수정을 캐시 무효화의 트리거로 간주해요.
  • 한 레이어가 무효화되면 그 뒤의 모든 레이어도 함께 무효화돼요. 베이스 이미지나 중간 레이어를 포함해 이전 레이어 중 하나라도 변경으로 인해 무효화되면, Docker는 그에 의존하는 이후 레이어들도 무효화되도록 보장해요. 이렇게 해서 빌드 과정의 일관성을 유지하고 불일치를 방지해요.

Dockerfile을 작성하거나 편집할 때는 불필요한 캐시 미스가 없는지 주의해서, 빌드가 가능한 한 빠르고 효율적으로 실행되도록 해야 해요.

직접 해보기

이 실습 가이드에서는 Node.js 애플리케이션에서 Docker 빌드 캐시를 효과적으로 사용하는 방법을 배워요.

애플리케이션 빌드

  • Docker Desktop을 다운로드하고 설치해요.

  • 터미널을 열고 이 샘플 애플리케이션을 클론해요.

    $ git clone https://github.com/dockersamples/todo-list-app
    
  • todo-list-app 디렉터리로 이동해요.

    $ cd todo-list-app
    

    이 디렉터리 안에 다음 내용을 담은 Dockerfile 파일이 있어요.

    FROM node:22-alpine
    WORKDIR /app
    COPY . .
    RUN yarn install --production
    EXPOSE 3000
    CMD ["node", "./src/index.js"]
    
  • 다음 명령을 실행해 Docker 이미지를 빌드해요.

    $ docker build .
    

    빌드 과정의 결과는 이래요.

    [+] Building 20.0s (10/10) FINISHED
    

    첫 번째 줄은 전체 빌드 과정이 20.0초가 걸렸다는 뜻이에요. 첫 빌드는 의존성을 설치하느라 다소 시간이 걸릴 수 있어요.

  • 변경 없이 다시 빌드해요. 이제 소스 코드나 Dockerfile을 변경하지 않고 docker build 명령을 다시 실행해 보세요.

    $ docker build .
    

    명령과 컨텍스트가 그대로라면 첫 빌드 이후의 빌드들은 캐싱 메커니즘 덕분에 더 빨라져요. Docker는 빌드 과정에서 생성된 중간 레이어를 캐시해요. Dockerfile이나 소스 코드를 변경하지 않고 이미지를 다시 빌드하면 Docker는 캐시된 레이어를 재사용할 수 있어 빌드 과정을 크게 단축해요.

    [+] Building 1.0s (9/9) FINISHED                                                                            docker:desktop-linux
     => [internal] load build definition from Dockerfile                                                                        0.0s
     => => transferring dockerfile: 187B                                                                                        0.0s
     ...
     => [internal] load build context                                                                                           0.0s
     => => transferring context: 8.16kB                                                                                         0.0s
     => CACHED [2/4] WORKDIR /app                                                                                               0.0s
     => CACHED [3/4] COPY . .                                                                                                   0.0s
     => CACHED [4/4] RUN yarn install --production                                                                              0.0s
     => exporting to image                                                                                                      0.0s
     => => exporting layers                                                                                                     0.0s
     => => exporting manifest
    

    이후 빌드는 캐시된 레이어를 활용해 단 1.0초 만에 완료됐어요. 의존성 설치 같은 시간이 오래 걸리는 단계를 반복할 필요가 없어요.

    단계 설명 소요 시간(1번째 실행) 소요 시간(2번째 실행)
    1 Load build definition from Dockerfile 0.0초 0.0초
    2 Load metadata for docker.io/library/node:22-alpine 2.7초 0.9초
    3 Load .dockerignore 0.0초 0.0초
    4 Load build context (컨텍스트 크기: 4.60MB) 0.1초 0.0초
    5 Set the working directory (WORKDIR) 0.1초 0.0초
    6 Copy the local code into the container 0.0초 0.0초
    7 Run yarn install --production 10.0초 0.0초
    8 Exporting layers 2.2초 0.0초
    9 Exporting the final image 3.0초 0.0초

    docker image history 출력을 다시 보면, Dockerfile의 각 명령이 이미지의 새 레이어가 되는 걸 볼 수 있어요. 이미지에 변경을 가했을 때 yarn 의존성을 다시 설치해야 했던 걸 기억하시나요? 이 문제를 해결할 방법이 있을까요? 빌드할 때마다 같은 의존성을 다시 설치하는 건 별로 합리적이지 않잖아요. 이 문제를 해결하려면 정말 무효화할 필요가 없을 때는 의존성 캐시가 유효하게 유지되도록 Dockerfile을 재구성하면 돼요. Node 기반 애플리케이션에서 의존성은 package.json 파일에 정의돼요. 이 파일이 변경되면 의존성을 다시 설치하고 싶지만, 파일이 변경되지 않았다면 캐시된 의존성을 사용하고 싶을 거예요. 그러니 그 파일만 먼저 복사하고, 의존성을 설치한 다음, 나머지를 모두 복사하면 돼요. 그러면 package.json 파일에 변경이 있을 때만 yarn 의존성을 다시 만들면 돼요.

  • package.json 파일을 먼저 복사하고, 의존성을 설치한 다음, 나머지를 모두 복사하도록 Dockerfile을 업데이트해요.

    FROM node:22-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production 
    COPY . . 
    EXPOSE 3000
    CMD ["node", "src/index.js"]
    
  • Dockerfile이 있는 같은 폴더에 다음 내용을 담은 .dockerignore 파일을 만들어요.

    node_modules
    
  • 새 이미지를 빌드해요.

    $ docker build .
    

    그러면 다음과 비슷한 출력이 보여요.

    [+] Building 16.1s (10/10) FINISHED
    => [internal] load build definition from Dockerfile                                               0.0s
    => => transferring dockerfile: 175B                                                               0.0s
    => [internal] load .dockerignore                                                                  0.0s
    => => transferring context: 2B                                                                    0.0s
    => [internal] load metadata for docker.io/library/node:22-alpine                                  0.0s
    => [internal] load build context                                                                  0.8s
    => => transferring context: 53.37MB                                                               0.8s
    => [1/5] FROM docker.io/library/node:22-alpine                                                    0.0s
    => CACHED [2/5] WORKDIR /app                                                                      0.0s
    => [3/5] COPY package.json yarn.lock ./                                                           0.2s
    => [4/5] RUN yarn install --production                                                           14.0s
    => [5/5] COPY . .                                                                                 0.5s
    => exporting to image                                                                             0.6s
    => => exporting layers                                                                            0.6s
    => => writing image     
    sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25        0.0s
    => => naming to docker.io/library/node-app:2.0                                                 0.0s
    

    모든 레이어가 다시 빌드된 걸 볼 수 있어요. Dockerfile을 꽤 많이 변경했으니 당연한 결과예요.

  • 이제 src/static/index.html 파일을 변경해 봐요. (예: 제목을 "The Awesome Todo App"으로 변경)

  • Docker 이미지를 빌드해요. 이번에는 출력이 조금 달라질 거예요.

    $ docker build -t node-app:3.0 .
    

    그러면 다음과 비슷한 출력이 보여요.

    [+] Building 1.2s (10/10) FINISHED 
    => [internal] load build definition from Dockerfile                                               0.0s
    => => transferring dockerfile: 37B                                                                0.0s
    => [internal] load .dockerignore                                                                  0.0s
    => => transferring context: 2B                                                                    0.0s
    => [internal] load metadata for docker.io/library/node:22-alpine                                  0.0s 
    => [internal] load build context                                                                  0.2s
    => => transferring context: 450.43kB                                                              0.2s
    => [1/5] FROM docker.io/library/node:22-alpine                                                    0.0s
    => CACHED [2/5] WORKDIR /app                                                                      0.0s
    => CACHED [3/5] COPY package.json yarn.lock ./                                                    0.0s
    => CACHED [4/5] RUN yarn install --production                                                     0.0s
    => [5/5] COPY . .                                                                                 0.5s 
    => exporting to image                                                                             0.3s
    => => exporting layers                                                                            0.3s
    => => writing image     
    sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda       0.0s
    => => naming to docker.io/library/node-app:3.0                                                 0.0s
    

    먼저 빌드가 훨씬 빨라진 걸 눈치챘을 거예요. 여러 단계에서 이전에 캐시된 레이어를 사용하는 게 보여요. 좋은 소식이죠. 여러분은 빌드 캐시를 활용하고 있는 거예요. 이 이미지와 그 업데이트를 푸시하고 풀하는 것도 훨씬 빨라질 거예요.

이런 최적화 기법을 따르면 Docker 빌드를 더 빠르고 효율적으로 만들어, 더 빠른 반복 주기와 향상된 개발 생산성을 얻을 수 있어요.

추가 자료

다음 단계

이제 Docker 빌드 캐시를 효과적으로 사용하는 법을 이해했으니, 멀티 스테이지 빌드에 대해 배워 볼 준비가 됐어요.

더 알아보기 (Learn more)