모노레포의 웹 서비스 세 개를 각각의 이미지로 굽습니다. Next.js standalone 모드와 Turborepo의 --docker 옵션을 조합해 node_modules를 걷어내고 이미지를 가볍게 만드는 방법을 정리했습니다.
0. 프롤로그
이 글은 뮤즈라이브에서 웹 서비스 3개를 모노레포 구조에서 Turborepo의 공식 문서를 참조하여 Next.js의 standalone 모드와 Docker를 활용한 모노레포 환경 설정 및 최적화 방법을 설명합니다.
목표
Next.js standalone 모드 설명
Turborepo를 활용한 모노레포 구성 이해
Docker를 통해 효율적인 최적화 및 배포 방법
1. Next.js standalone 란?
Next.js standalone 모드는 애플리케이션을 컴파일하여 런타임에 불필요한 파일을 제거하고, 최적화된 상태로 제공하는 기능입니다. 이 모드는 애플리케이션을 더 작고
독립적인 단위로 패키징하여, 서버 실행 환경(Docker 등)에서 경량화와 성능 향상을 도모할 수 있습니다.
주요 특징
최소화된 파일 크기: 번들링 과정에서 node_modules 및 불필요한 파일을 배제하고, 필수 파일들만 포함합니다.
독립적인 실행 환경: 애플리케이션 실행을 위해 Next.js 소스 코드와 관련된 종속 파일만 필요합니다.
서버 배포 최적화: Docker와 같은 컨테이너 기반 시스템과 결합하여 보다 효과적인 배포가 가능해집니다.
빠른 로딩 및 적은 리소스 사용: 실행 시 필요한 최소한의 파일과 리소스만 포함되기 때문에 초기화 속도와 성능이 향상됩니다.
CDN과 조합: 캐싱 기능 및 네트워크 요청 최적화를 통해 전 세계적으로 빠르고 안정적인 콘텐츠 제공이 가능합니다.
이 모드를 활용하면 Next.js 애플리케이션 배포 및 운영 관리를 더욱 효율적으로 할 수 있습니다.
FROM node:20WORKDIR /usr/src/app# Copy root package.json and lockfileCOPY package.json ./COPY package-lock.json ./# Copy the docs package.jsonCOPY apps/web1/package.json ./apps/web1/package.jsonRUN npm install# Copy app sourceCOPY . .EXPOSE 8080CMD [ "node", "apps/docs/server.js" ]
의존성 문제 발생
패키지 설치 문제: apps/web1에 필요하지 않은 apps/web2의 의존성이 바뀌는 경우 불필요한 설치가 생깁니다. 또한 lock파일 또한 자주 바뀌어 이 또한 불필요한 빌드가 생길 수 있습니다.
의존성 충돌: 여러 패키지가 같은 의존성을 다르게 요구하는 경우, 루트에서 설치된 의존성과 앱별 의존성이 충돌할 가능성이 있습니다.
해결책
Dockerfile에 대한 입력을 엄격히 필요한 것으로만 정리하는 것입니다. Turborepo는 간단한 솔루션을 제공합니다
bash
pnpm turbo prune --scope=web1 --docker
이 명령을 실행하면 모노레포의 정리된 버전이 생성되어, ./out 디렉토리에 docs 이후에 달라지는 작업 공간만 포함됩니다.
핵심은 잠금 파일을 정리하여 필요한 파일만 node_modules에 다운로드되도록 하는 것입니다. 기본적으로 turbo prune은 모든 관련 파일을 ./out에 넣지만,
Docker 캐시 최적화를 위해 이상적으로는 파일을 두 단계로 복사합니다.
out
// json: 의존성 설치를 위해 사용하는 폴더
├── json
│ └── apps
│ └── web1
│ └── package.json
│ └── packages
│ └── ui
│ └── package.json
// full: 실제 빌드에 필요한 모든 파일을 포함
├── full
│ └── apps
│ └── wseb1
│ └── package.json
│ └── packages
│ └── ui
│ └── package.json
└── package-lock.json
솔류션을 적용한 DockerFile
dockerfile
FROM node:alpine AS builderRUN apk add --no-cache libc6-compatRUN apk update# Set working directoryWORKDIR /appRUN pnpm global add turboCOPY . .RUN turbo prune --scope=web1 --docker# Add lockfile and package.json's of isolated subworkspaceFROM node:alpine AS installerRUN apk add --no-cache libc6-compatRUN apk updateWORKDIR /app# First install the dependencies (as they change less often)COPY .gitignore .gitignoreCOPY--from=builder /app/out/json/ .COPY--from=builder /app/out/pnpm.lock ./pnpm.lockRUN pnpm install# Build the projectCOPY--from=builder /app/out/full/ .COPY turbo.json turbo.jsonRUN pnpm turbo run build --filter=web1...FROM node:alpine AS runnerWORKDIR /app# Don't run production as rootRUN addgroup --system --gid 1001 nodejsRUN adduser --system --uid 1001 nextjsUSER nextjsCOPY--from=installer /app/apps/web1/next.config.js .COPY--from=installer /app/apps/web1/package.json .# Automatically leverage output traces to reduce image size# https://nextjs.org/docs/advanced-features/output-file-tracingCOPY--from=installer--chown=nextjs:nodejs /app/apps/web1/.next/standalone ./COPY--from=installer--chown=nextjs:nodejs /app/apps/web1/.next/static ./apps/web1/.next/staticCMD node apps/web1/server.js
static s3 upload
빌드 과정에서 나오는 css, js, html을 cdn으로 올려
static 파일을 Amazon S3에 업로드하면, 빠르고 안정적으로 해당 리소스를 제공할 수 있습니다. 이를 통해 서버 부하를 줄이고, 클라이언트가 가까운 CDN 엣지에서 파일을
다운로드하도록 최적화할 수 있습니다.
./out/apps/web1/.next/static: Next.js 빌드 시 생성된 정적 파일들이 위치하는 디렉토리입니다.
./out/apps/web1/public: 퍼블릭 폴더에 포함된 정적 리소스 파일들입니다.
s3://your-cdn-bucket-name: 대상 S3 버킷 이름으로 적절히 변경합니다.
해당 파일들은 클라이언트에서 CDN URL을 통해 접근할 수 있으므로, Next.js 설정에서 assetPrefix를 활용해 빌드된 출력물을 S3를 통해 제공받도록 수정해야 **your-cdn-bucket-name******합니다.