← 전체 글로 돌아가기

Docker

Dockerfile에서 COPY 순서를 바꿨더니 빌드 시간이 확 줄었다

package.json을 소스 코드보다 먼저 COPY하는 패턴 하나로 npm install 캐시가 살아난다.

Docker 이미지를 빌드할 때 코드 한 줄 바꿨을 뿐인데 매번 npm install이 처음부터 돌아가서 2분씩 기다린 적이 있다. 원인은 Dockerfile의 COPY 순서였다.

Docker 레이어 캐시의 동작 방식

Dockerfile의 각 명령은 레이어로 쌓인다. 중요한 건, 특정 레이어에 변화가 생기면 그 이후 레이어는 모두 캐시가 무효화된다는 점이다.

잘못된 패턴은 이렇다.

# 잘못된 순서
FROM node:20-alpine
WORKDIR /app

COPY . .          # 소스 코드 전체를 먼저 복사
RUN npm install   # 파일 하나라도 바뀌면 이 단계부터 다시 실행
RUN npm run build

소스 코드를 먼저 모두 복사하면, .ts 파일 하나가 바뀔 때마다 COPY 레이어가 바뀌고, 그 아래 npm install도 캐시가 무효화된다. 결과적으로 의존성이 전혀 바뀌지 않았음에도 매번 패키지를 새로 설치하게 된다.

올바른 COPY 순서

FROM node:20-alpine
WORKDIR /app

# 의존성 파일만 먼저 복사
COPY package.json package-lock.json ./
RUN npm ci

# 그 다음 소스 코드 복사
COPY . .
RUN npm run build

이렇게 하면 package.jsonpackage-lock.json이 바뀌지 않는 한 npm ci 레이어의 캐시가 유지된다. 소스 코드만 수정했을 때는 마지막 COPY . .부터만 다시 실행된다.

멀티 스테이지 빌드와 함께 쓰면 더 효과적이다

# 빌드 스테이지
FROM node:20-alpine AS builder
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

# 실행 스테이지
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

CMD ["node", "server.js"]

빌드 스테이지에서 의존성을 설치하고, 최종 이미지에는 실행에 필요한 파일만 복사하면 이미지 크기도 같이 줄어든다.

.dockerignore도 같이 확인한다

node_modules
.next
.git
*.log

.dockerignore 없이 COPY . .를 쓰면 node_modules가 통째로 복사돼 COPY 레이어가 느려지고, 로컬 node_modules의 바이너리가 컨테이너 OS와 맞지 않는 문제도 생긴다. .dockerignore는 Dockerfile과 같은 디렉토리에 두면 된다.