최근에 next.js로 간단한 페이지만 추가한 상태에서 도커 이미지를 빌드했는데 도커 이미지가 2.19G가 넘어가는 것을 확인했습니다. 😭
결과부터 미리 말하자면 2.19G에서 850MB로 약 60%정도 이미지 크기를 줄였으며, 빌드 시간 또한 241.7s에서 198s로 최적화되었습니다.
그럼 어떻게 최적화를 진행하였는지 기록하도록 하겠습니다.
가즈아 🔥🔥
가장 큰 이유는 EC2의 디스크 공간을 더 적게 차지하기 때문입니다.
대략적으로 SSD Volume이 추가될 때 마다 $0.10/GB/월 정도의 비용이 발생하는데 최적화되지 않은 이미지가 계속 추가된다면 비용은 기하급수적으로 늘어날 수도 있겠죠.
또한 이미지를 최적화하면 다운로드 및 전송 시간이 줄어듭니다.
시간은 소중하죠..
먼저 최적화 전의 Dockerfile
을 살펴보겠습니다.
# 1. Base image
FROM node:20.9.0 AS base
# 2. Dependencies
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
COPY .yarn ./.yarn
COPY package.json yarn.lock ./
RUN yarn set version berry
# 3. Builder
FROM deps AS builder
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED 1
COPY --from=deps /app/.yarn ./.yarn
COPY --from=deps /app/yarn.lock ./
COPY . .
ARG SERVER_ENV
RUN yarn
RUN yarn build:dev
# 4. Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN cd /app && echo 'YARN VERSION IN BUILDER: ' && yarn --version
# RUN yarn rebuild && yarn build
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/next.config.js ./
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/.yarn ./.yarn
COPY --from=builder --chown=nextjs:nodejs /app/yarn.lock ./yarn.lock
COPY --from=builder --chown=nextjs:nodejs /app/.yarnrc.yml ./.yarnrc.yml
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
RUN rm -rf /app/.yarn/unplugged && yarn rebuild
RUN chown -R nextjs:nodejs /app/.next
RUN echo "YARN VERSION IN RUNNER: " && yarn --version
USER nextjs
EXPOSE 3000
CMD ["yarn", "start"]
위의 코드는 다중 단계 빌드를 사용하여 Next.js 애플리케이션을 빌드합니다.
상세하게 나눠 설명하자면
FROM node:20.9.0 AS base: Node.js 20.9.0 버전을 베이스 이미지로 지정합니다.
.yarn 디렉터리와 package.json, yarn.lock 파일을 복사하여 이미지 내에 추가합니다.
yarn set version berry 명령을 사용하여 Yarn Berry를 설치합니다.
FROM deps AS builder: 앞서 생성한 의존성 이미지를 기반으로 새로운 단계를 생성합니다.
애플리케이션의 소스 코드를 복사하고 필요한 의존성을 설치합니다.
yarn build:dev를 실행하여 개발용 빌드를 수행합니다.
빌드된 애플리케이션 파일 및 필요한 파일을 복사합니다.
yarn start를 실행하여 애플리케이션을 시작합니다.
첫 번째는 베이스로 사용한 Node 이미지 문제입니다.
기존에는 node:20.9.0 AS base
를 사용하고 있습니다.
이걸 alpine으로 변경해봅시다.
# FROM node:20.9.0 AS base
FROM node:20.9.0-alpine AS base
과연 얼마나 줄어들까?
alpine
태그는 Alpine Linux를 기반으로 한 경량화된 Docker 이미지를 가리킵니다. 따라서 base로 사용할 때는 alpine를 사용하는 것만으로도 이미지 크기를 크게 절약할 수 있습니다.
이미 크게 용량을 줄였지만 아직 더 줄일 요지는 있습니다.
도커 이미지 실행에 필요하지 않은 파일을 이미지에서 제거한다면 용량이 더 줄어들지 않을까요?
.dockerignore
를 활용하면 도커 이미지에 불필요한 파일들을 포함시키지 않을 수 있습니다.
.dockerignore
파일node_modules
npm-debug.log
*.log
*.swp
.DS_Store
.next
.vscode
README.md
아직 낭비되고 있는 공간이 분명 있을 겁니다.
한번 이미지 분석 도구를 통해 낭비되고 있는 공간을 확인해봅시다.
여기서는 도커 이미지 분석툴인 dive
를 사용할 겁니다.
dive
설치법brew install dive
dive
사용법dive 이미지_이름:태그
명령어를 치면 Fetching image...
라는 문구가 뜨면서 분석 결과를 확인해 볼 수 있습니다.
분석 결과를 보니 500 MB를 아낄 수 있다고 나오네요.
아래에 낭비되고 있는 불필요한 파일들이 나옵니다.
아.. 불필요하게 cache 파일들이 추가되고 있었네요.
빌드를 다 한 후에는 cache 파일을 더이상 필요가 없습니다.
Runner 스탭에서 앱을 실행하기 전에 불필요한 cache 파일을 지우도록 수정합시다.
RUN rm -rf /app/.yarn/unplugged && yarn rebuild && rm -rf /app/.yarn/cache
RUN chown -R nextjs:nodejs /app/.next && rm -rf /app/.next/cache
최종적으로 500mb를 더 절약하며 Image efficiency score를 99%로 달성할 수 있었습니다.
1%는 도저히 발견할 수가..
만약 CI 단계에서 이미 린트 체크를 하고 있다면 프로덕션 빌드 단계에서 린트 체크를 스킵할 수가 있습니다. 그러면 불필요한 빌드 시간을 단축시킬 수 있으며 이미지 크기도 추가적으로 더 아낄 수 있습니다.
.next.config.js
를 수정합니다.
eslint: {
ignoreDuringBuilds: true
}
도커 이미지를 최적화해서 불필요한 시간과 비용을 아낍시다.
끝 ..!!