← 글 목록

notes

Docker 이미지 최적화 가이드

Docker 이미지 레이어·빌드 캐시·멀티스테이지 기반 최적화 가이드.

모노레포(특히 Turborepo + Next)를 Docker로 올리다 보면 이미지가 수 GB까지 커진다. node_modules 통째, 빌드 도구, 안 쓰는 워크스페이스까지 들어가서다. 레이어·캐시·멀티스테이지·prune을 맞춰 가며 줄인 메모.

멀티스테이지 — 빌드랑 런타임을 가른다

빌드에만 필요한 도구를 최종 이미지에 안 넣는 게 제일 효과 컸다.

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"]

Next는 output: 'standalone'을 켜 두면 런타임에 필요한 파일만 추려 준다. node_modules 전체를 복사하는 것보다 훨씬 작다.

.dockerignore

컨텍스트에 node_modules, .git, .next, 로그, .env*가 들어가면 빌드가 느리고 캐시도 자주 깨진다.

node_modules
.git
.next
*.log
.env*
.turbo
.cache
dist

레이어 순서 — 자주 안 바뀌는 것부터

# 의존성은 package*.json만 바뀌면 재설치
COPY package.json package-lock.json ./
RUN npm ci

# 소스는 자주 바뀌니까 나중에
COPY . .
RUN npm run build

COPY . .를 먼저 하고 npm install하면 코드 한 줄 고쳐도 install 레이어가 매번 다시 돈다.

베이스 이미지

가능하면 node:20-alpine / slim. 디버깅할 때만 일반 이미지. glibc 이슈 나는 네이티브 모듈 있으면 alpine 대신 slim을 고른다.

의존성 스테이지 분리

FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

Turborepo — prune

모노레포 전체 lockfile이 바뀌면 관련 없는 앱 Docker 빌드까지 npm install이 다시 도는 문제가 있다. turbo prune <workspace> --docker가 out/json(설치용)랑 out/full(소스)를 나눠 줘서, 의존성 레이어와 소스 레이어를 분리할 수 있다.

turbo prune web --docker

대략적인 Dockerfile 흐름:

FROM node:20-alpine AS base
WORKDIR /app
RUN npm i -g turbo pnpm

FROM base AS prepare
COPY . .
RUN turbo prune web --docker

FROM base AS installer
COPY --from=prepare /app/out/json/ .
RUN pnpm install
COPY --from=prepare /app/out/full/ .
RUN pnpm turbo run build --filter=web...

FROM base AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs \
 && adduser --system --uid 1001 nextjs
USER nextjs
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
CMD ["node", "apps/web/server.js"]

공식 가이드(turbo.build Docker)랑 Quick Starter 글이랑 같은 뼈대다. prune 때문에 필요한 워크스페이스·lock 조각만 들어가서 이미지·install 시간이 같이 줄어든다.

프로덕션 의존성만

멀티스테이지 없이 한 이미지에 넣을 때라도:

RUN npm ci --omit=dev

standalone을 쓰면 이 이슈는 대부분 사라진다.

정리

이미지 줄인 다음엔 서버 디스크에 쌓인 옛 태그는 auto clean / cron 쪽으로 넘긴다.