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 ci가 CACHED인지 확인했다. 이후 lockfile을 바꿨을 때는 설치가 다시 실행되어야 하므로, 캐시가 항상 유지되는 것이 목표는 아니다. 빌드 서버에서는 캐시 저장소를 추가할 수 있지만 먼저 Dockerfile 레이어 순서가 맞는지부터 확인하는 편이 비용이 적었다.
- 의존성 파일만 바뀌지 않으면 설치 레이어가 재사용된다.
- 소스와 빌드 산출물이 컨텍스트에서 제외된다.
- 실행 이미지에 개발용 파일이 불필요하게 들어가지 않는다.
- lockfile 변경 시 의도대로 새 설치가 일어난다.
빌드 시간을 줄인 핵심은 특별한 옵션이 아니라 변경 빈도가 다른 파일을 같은 레이어에 넣지 않은 것이었다. 캐시가 깨졌을 때 무조건 Docker를 의심하기보다 어느 COPY가 기준을 바꿨는지부터 보는 습관을 남겼다.