Next.js 프로젝트는 로컬에서는 잘 작동하지만,
Docker로 운영 환경에 배포하는 순간 전혀 다른 문제가 발생한다.
- 빌드 결과물과 전체 소스를 모두 포함해 이미지 용량이 1GB를 넘고
- 의존성은 바뀌지 않았는데도 매번 설치부터 다시 하게 되고
- 빌드 시간과 배포 속도는 점점 길어지며
- 실행 구조가 복잡해져 운영 환경에서 디버깅이나 관리가 번거로워진다
단순히 “실행되는 Docker 이미지”를 만드는 것과
운영 환경에서 최적화된 구조로 빠르고 안정적으로 배포하는 것은 다르다.
이 글에서는 위 문제들을 해결하기 위해
내가 직접 사용하고 있는 두 가지 최적화 전략을 공유한다.
- 멀티스테이지 빌드 (Multi-stage Build)
- Next.js standalone 모드
이 두 가지를 함께 적용하면
Docker 이미지 용량을 1/4로 줄이고, 빌드 시간과 배포 속도까지 개선할 수 있다.
1. 멀티스테이지 구성과 최적화 효과
멀티스테이지 빌드는 Dockerfile을 단계별로 분리해, 실행에 필요한 파일만 최종 이미지에 포함시키는 방식이다.
Next.js처럼 빌드와 실행 환경이 명확히 구분되는 프로젝트에서 특히 유용하다.
1단계: Base 스테이지
FROM node:18-alpine AS base
- Alpine 이미지를 사용해 가능한 한 가벼운 Node.js 환경을 구성한다.
- 모든 스테이지에서 이 이미지를 공유하면, Docker 캐시 효율이 향상된다.
2단계: deps 스테이지 (의존성 설치)
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
- 의존성 설치를 별도의 스테이지로 분리하면, 이후 단계에서 소스 코드가 변경되어도 Docker는 이 레이어를 캐시로 재사용한다.
- COPY package*.json → RUN npm ci 순서를 유지하면 캐시가 효율적으로 적용되고, 전체 빌드 속도가 빨라진다.
3단계: builder 스테이지 (앱 빌드)
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
- 소스 코드와 의존성을 조합해 .next/ 디렉토리에 빌드 결과를 생성한다.
- COPY . .은 가장 마지막에 위치시켜 소스 변경 시 캐시 무효화를 최소화한다.
4단계: runner 스테이지 (standalone 미적용 기준)
FROM base AS runner
WORKDIR /app
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]
- 실행에 필요한 디렉토리만 복사하여 최종 실행 환경을 구성한다.
- standalone 모드를 적용하지 않은 경우에는 .next, node_modules, public, package.json이 모두 필요하다.
unoptimized vs. multistage
- 전체 프로젝트를 포함한 Dockerfile은 이미지 용량이 1GB 이상까지 커질 수 있다.
- 멀티스테이지 빌드를 적용하면 실행에 필요한 파일만 포함되어 약 600MB가 줄어든 것을 볼 수 있다.
2. Next.js standalone 모드
standalone 모드란?
Next.js에서 output: 'standalone'을 설정하면, 실행에 필요한 서버 코드와 의존성만 자동으로 .next/standalone에 추출된다.
이 구조는 단일 실행 파일처럼 동작하며, 복잡한 디렉토리 복사 없이 실행 가능하다.
설정 방법
// next.config.js
const nextConfig = {
output: 'standalone',
};
module.exports = nextConfig;
실행 이미지 구성 방식 (standalone 적용 시)
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
- 실행에 필요한 서버 코드만 추출되어 이미지가 훨씬 가벼워진다.
- 복사해야 할 파일도 줄어들어 배포 구성이 간단해진다.
standalone 모드의 장점
- 이미지 용량이 줄어든다.
- 실행 구조가 단순해져 배포 속도가 빨라진다.
- 별도 설정 없이 실행 파일만으로 앱을 구동할 수 있다.
multistage vs. standalone
- 멀티스테이지만 적용한 경우에도 node_modules 등이 포함되어 이미지가 다소 크다.
- standalone을 적용하면 실행 파일만 남아 이미지의 사이즈가 최적화된다.
마무리
멀티스테이지 빌드는 실행에 꼭 필요한 파일만 최종 이미지에 포함시켜 용량을 줄이고, Docker 캐시를 적극 활용해 빌드 속도를 개선한다.
여기에 standalone 모드를 적용하면 복잡한 디렉토리 구성 없이 단일 실행 구조만으로도 앱을 운영할 수 있게 된다.
특히 output: 'standalone' 설정만으로 실행에 필요한 서버 코드와 의존성이 자동으로 추출되기 때문에, 이후 배포도 훨씬 간단해진다.
'React' 카테고리의 다른 글
React 상태가 변경되어도 리렌더링되지 않는 이유와 해결법 (1) | 2025.05.27 |
---|---|
Next.js standalone 배포 "sharp missing in production" 에러 해결하기 (0) | 2025.05.07 |
Portal과 Compound 패턴으로 Modal 만들기 (0) | 2025.04.29 |
공통 컴포넌트를 유연하게 설계하는 방법: Compound Component 패턴 적용하기 (1) | 2025.04.29 |
[React] 렌더링 성능 개선하기 (0) | 2022.04.19 |