🔍 검색 키워드: Docker 멀티스테이지 빌드, Dockerfile 캐시 성능, Docker 레이어 캐싱, 빌드 시간 단축
Docker 멀티스테이지 빌드 캐시 최적화하기
증상
Dockerfile로 이미지를 빌드할 때마다 다음과 같은 문제가 발생한다:
$ docker build -t myapp:latest .
Step 3/10 : RUN npm install # 매번 30초 이상 소요
Step 4/10 : RUN npm run build # 매번 60초 이상 소요
...
real 2m 15s
코드는 조금만 바뀌었는데도 빌드 시간이 계속 길어진다. CI/CD 파이프라인에서 빌드만 몇 분씩 걸려서 배포 속도가 느리다. 특히 npm, pip 같은 의존성 설치 단계가 매번 전부 다시 실행된다.
원인
Docker는 각 RUN, COPY, ADD 단계를 하나의 레이어로 취급하고, 파일이 변경되면 그 이후의 모든 레이어를 다시 빌드한다. 예를 들어:
FROM node:18
COPY . /app # 레이어1: 모든 파일 복사
WORKDIR /app
RUN npm install # 레이어2: 의존성 설치 (레이어1 변경되면 캐시 무효)
RUN npm run build # 레이어3: 빌드 (레이어2 변경되면 캐시 무효)
코드를 한 줄만 바꿔서 COPY . /app을 실행하면 npm install, npm run build가 모두 다시 실행된다. 즉, 캐시를 제대로 활용하지 못한다.
멀티스테이지 빌드를 사용하면서도 각 스테이지 간 의존성이 꼬여 있으면 캐시 효율이 떨어진다.
해결방법
1. 의존성 파일을 먼저 복사 (핵심!)
FROM node:18 AS builder
WORKDIR /app
# package.json, package-lock.json만 먼저 복사
COPY package*.json ./
# 의존성 설치 (코드 변경 시 캐시 유지)
RUN npm ci
# 이제 코드 복사
COPY . .
# 빌드
RUN npm run build
# 런타임 스테이지
FROM node:18-alpine
WORKDIR /app
# 의존성만 복사 (빌드 아티팩트 불필요)
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
캐시 효과:
package.json불변 → npm install 캐시 유지- 코드만 변경 → COPY . . 부터만 재실행
- 빌드 시간 30초 → 5초
2. Docker Buildkit으로 고급 캐싱 활용
// syntax=docker/dockerfile:1.4
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
// --mount=type=cache로 npm cache 디렉토리 보존
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
빌드 명령:
DOCKER_BUILDKIT=1 docker build -t myapp:latest .
효과:
- npm cache가 빌드 간에 유지되어 더 빠름
- 오프라인 설치 최적화
3. 불필요한 파일 제외 (.dockerignore)
.git
.gitignore
node_modules
npm-debug.log
.env
.env.local
dist
build
coverage
.DS_Store
README.md
효과:
- COPY . . 시 변경되지 않은 파일이 많으면 해시 계산이 더 오래 걸림
- .dockerignore로 제외하면 캐시 재계산 범위 축소
4. 여러 스테이지에서 캐시 공유
// syntax=docker/dockerfile:1.4
FROM node:18 AS dependencies
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
// 빌드 스테이지1
FROM dependencies AS builder1
COPY . .
RUN npm run build
// 빌드 스테이지2 (테스트)
FROM dependencies AS tester
COPY . .
RUN npm test
// 최종 스테이지
FROM node:18-alpine
WORKDIR /app
COPY --from=builder1 /app/dist ./dist
COPY --from=dependencies /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
5. 번들 사이즈 최소화 (캐시 영향 없지만 이미지 크기 감소)
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
// 크기 최소화: dependencies만 복사, devDependencies 제외
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
// 본번 의존성만 설치 (devDependencies 없음)
COPY package*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["node", "dist/index.js"]
정리표
| 문제 | 원인 | 해결법 |
|---|---|---|
| npm install 매번 재실행 | COPY . . 후 RUN npm install | package.json만 먼저 복사 |
| 캐시가 가끔만 작동 | 불필요 파일 포함 시 해시 변경 | .dockerignore 작성 |
| 빌드 시간 여전히 길다 | npm cache 미보존 | Buildkit + --mount=cache 활용 |
| 이미지 크기 커짐 | devDependencies 포함 | npm ci --omit=dev |
| 멀티스테이지 간 캐시 미공유 | 각 스테이지가 독립적 | dependencies AS 공용 스테이지 생성 |
TIP: docker build --progress=plain으로 상세 로그 확인, docker image history myapp:latest로 각 레이어 크기 확인 가능. 캐시 강제 무효화는 docker build --no-cache 또는 COPY . . --chown=node:node 같은 타임스탐프 변경 명령으로.
댓글 없음:
댓글 쓰기