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.json과 package-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과 같은 디렉토리에 두면 된다.