코드 한 줄만 고쳤는데 docker build가 매번 의존성 설치부터 다시 한다면, 레이어 캐시가 깨지고 있다는 뜻입니다. 도커는 Dockerfile의 각 명령을 레이어로 쌓고, 바뀐 게 없으면 이전 빌드 결과를 그대로 재사용합니다. 문제는 한 레이어가 무효화되면 그 아래 모든 레이어도 함께 무효화된다는 점입니다. 그래서 "무엇을 먼저 쓰느냐"가 빌드 속도를 좌우합니다.
캐시가 깨지는 규칙
각 명령마다 도커는 캐시 키를 계산합니다. RUN은 명령 문자열로, COPY/ADD는 복사 대상 파일의 내용 해시로 키를 만듭니다. 즉 COPY . .을 위쪽에 두면 소스 파일 하나만 바뀌어도 그 아래 RUN npm ci까지 전부 다시 실행됩니다.
핵심 — 안 바뀌는 것을 위로
자주 안 바뀌는 의존성 정의를 먼저 복사·설치하고, 자주 바뀌는 소스는 맨 나중에 복사합니다.
FROM node:20-slim
WORKDIR /app
# 1) 거의 안 바뀌는 의존성 정의만 먼저
COPY package.json package-lock.json ./
RUN npm ci
# 2) 자주 바뀌는 소스는 맨 마지막
COPY . .
RUN npm run build
CMD ["node", "dist/main.js"]
이렇게 하면 소스만 고쳤을 때 npm ci 레이어가 캐시에서 그대로 재사용돼 빌드가 수십 초에서 수 초로 줄어듭니다.
순서 비교
| 순서 | 소스 1줄 변경 시 | 결과 |
|---|---|---|
COPY . . 먼저 → RUN npm ci | 의존성 재설치 매번 | 느림 |
COPY package*.json → RUN npm ci → COPY . . | 의존성 캐시 재사용 | 빠름 |
캐시 마운트로 한 번 더
BuildKit의 --mount=type=cache를 쓰면 레이어 캐시가 깨져도 패키지 매니저 캐시 디렉터리는 빌드 간에 유지됩니다.
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm \
npm ci
lock 파일이 바뀌어 RUN이 다시 돌더라도 이미 받아둔 패키지는 캐시 디렉터리에서 재사용해 네트워크 다운로드를 건너뜁니다.
점검 체크리스트
DOCKER_BUILDKIT=1 docker build -t myapp . # 빌드 1회
docker build -t myapp . # 다시 빌드 → CACHED 표시 확인
docker history myapp # 레이어별 크기·생성 명령
두 번째 빌드 로그에서 CACHED가 의존성 단계까지 떠야 순서가 제대로 잡힌 것입니다. CACHED가 안 보이면 그 위 어떤 COPY가 캐시를 깨고 있는지 거슬러 올라가 봅니다.
Dockerfile 순서를 바꿔가며 CACHED 표시와 빌드 시간 변화를 직접 확인하는 실습은 도커 트랙에서 회원가입 없이 무료로 해볼 수 있습니다.