← 전체 글로 돌아가기

Docker

Docker 빌드가 느려졌을 때 레이어 순서를 바꿔 캐시를 되살린 비교 노트

소스 한 줄을 고칠 때마다 의존성을 다시 설치하던 Dockerfile을 단계별 캐시 관점에서 비교했다.

40초가 된 빌드

Node 기반 웹 앱에서 문구 하나를 고칠 때도 npm ci가 다시 실행됐다. 처음에는 패키지 저장소가 느린 날이라고 넘겼지만, 같은 커밋을 두 번 빌드해도 의존성 설치 단계가 매번 무효화되는 것을 확인했다. Dockerfile이 COPY . . 뒤에 설치 명령을 두고 있었기 때문이다.

FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
CMD ["npm", "start"]

소스 파일이 바뀌면 COPY . . 레이어가 달라지고, 그 다음 레이어도 연쇄적으로 다시 만들어진다.

바뀌는 파일과 잘 안 바뀌는 파일 분리

의존성 목록을 먼저 복사한 뒤 설치하도록 순서를 바꿨다.

FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS run
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.next ./.next
COPY --from=build /app/public ./public
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/node_modules ./node_modules
CMD ["npm", "start"]

package-lock.json을 빼고 package.json만 복사하면 잠금파일 변경을 놓칠 수 있어 둘을 함께 사용했다. 또 .dockerignore.git, node_modules, .next, .env*를 넣어 빌드 컨텍스트와 비밀값이 이미지로 들어가지 않게 했다.

printf 'node_modules
.next
.git
.env*
' > .dockerignore
docker build --progress=plain -t sample-web:cache-test .
docker build --progress=plain -t sample-web:cache-test .

숫자보다 비교할 로그

두 번째 빌드에서 npm ciCACHED인지 확인했다. 이후 lockfile을 바꿨을 때는 설치가 다시 실행되어야 하므로, 캐시가 항상 유지되는 것이 목표는 아니다. 빌드 서버에서는 캐시 저장소를 추가할 수 있지만 먼저 Dockerfile 레이어 순서가 맞는지부터 확인하는 편이 비용이 적었다.

  • 의존성 파일만 바뀌지 않으면 설치 레이어가 재사용된다.
  • 소스와 빌드 산출물이 컨텍스트에서 제외된다.
  • 실행 이미지에 개발용 파일이 불필요하게 들어가지 않는다.
  • lockfile 변경 시 의도대로 새 설치가 일어난다.

빌드 시간을 줄인 핵심은 특별한 옵션이 아니라 변경 빈도가 다른 파일을 같은 레이어에 넣지 않은 것이었다. 캐시가 깨졌을 때 무조건 Docker를 의심하기보다 어느 COPY가 기준을 바꿨는지부터 보는 습관을 남겼다.