Docker 이미지가 1GB를 넘어가면 배포 시간이 길어지고 보안 취약점도 늘어납니다. 멀티스테이지 빌드를 활용하면 이미지 크기를 90% 이상 줄일 수 있습니다.
When Docker images exceed 1GB, deployment takes longer and security vulnerabilities increase. Using multi-stage builds can reduce image size by over 90%.
멀티스테이지 빌드란? What is Multi-Stage Build?
하나의 Dockerfile에서 여러 FROM을 사용하여 빌드 환경과 실행 환경을 분리합니다. 빌드에 필요한 도구들은 최종 이미지에 포함되지 않습니다.
Use multiple FROM statements in a single Dockerfile to separate build and runtime environments. Build tools are not included in the final image.
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/index.js"]
# 결과: ~1.2GB 이미지 (node_modules, 빌드 도구 모두 포함)
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/index.js"]
# Result: ~1.2GB image (includes node_modules, build tools)
# Stage 1: 빌드 환경
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: 실행 환경 (최소한만 포함)
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
# 결과: ~150MB 이미지!
# Stage 1: Build environment
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Runtime environment (minimal)
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
# Result: ~150MB image!
레이어 캐싱 최적화 Layer Caching Optimization
Dockerfile의 각 명령어는 레이어를 생성합니다. 변경되지 않은 레이어는 캐시됩니다. 자주 변경되는 내용은 아래에, 변경이 적은 내용은 위에 배치하세요.
Each Dockerfile instruction creates a layer. Unchanged layers are cached. Put frequently changing content at the bottom, rarely changing content at the top.
FROM node:18
WORKDIR /app
COPY . .
RUN npm install # 코드 변경 시마다 재실행
FROM node:18
WORKDIR /app
COPY package*.json ./ # 의존성 파일만 먼저
RUN npm ci # 캐시됨 (package.json 변경 없으면)
COPY . . # 소스 코드
RUN npm run build
FROM node:18
WORKDIR /app
COPY . .
RUN npm install # Re-runs on every code change
FROM node:18
WORKDIR /app
COPY package*.json ./ # Dependency files first
RUN npm ci # Cached if package.json unchanged
COPY . . # Source code
RUN npm run build
베이스 이미지 선택 Choosing Base Images
| 이미지 | 크기 | 용도 |
|---|---|---|
node:18 |
~1GB | 개발 환경, 빌드 스테이지 |
node:18-slim |
~200MB | 프로덕션 (일반) |
node:18-alpine |
~50MB | 프로덕션 (최적) |
| Image | Size | Use Case |
|---|---|---|
node:18 |
~1GB | Development, build stage |
node:18-slim |
~200MB | Production (general) |
node:18-alpine |
~50MB | Production (optimal) |
⚠️ Alpine 주의사항 ⚠️ Alpine Caveats
Alpine은 musl libc를 사용합니다. 일부 npm 패키지가 호환되지 않을 수 있습니다. bcrypt, sharp 같은 네이티브 바이너리 패키지 사용 시 테스트 필요.
Alpine uses musl libc. Some npm packages may not be compatible. Test when using native binary packages like bcrypt, sharp.
프로덕션 Dockerfile 모범 사례 Production Dockerfile Best Practices
# 1. 빌드 스테이지
FROM node:18-alpine AS builder
WORKDIR /app
# 의존성 레이어 캐싱
COPY package*.json ./
RUN npm ci --only=production
# 소스 복사 및 빌드
COPY . .
RUN npm run build
# 2. 프로덕션 스테이지
FROM node:18-alpine
WORKDIR /app
# 보안: non-root 사용자
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 필요한 파일만 복사
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
# 환경 변수
ENV NODE_ENV=production
ENV PORT=3000
# 사용자 전환
USER nextjs
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -q --spider http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "dist/index.js"]
# 1. Build stage
FROM node:18-alpine AS builder
WORKDIR /app
# Dependency layer caching
COPY package*.json ./
RUN npm ci --only=production
# Copy source and build
COPY . .
RUN npm run build
# 2. Production stage
FROM node:18-alpine
WORKDIR /app
# Security: non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Copy only required files
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
# Environment variables
ENV NODE_ENV=production
ENV PORT=3000
# Switch user
USER nextjs
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -q --spider http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "dist/index.js"]
.dockerignore 활용 Using .dockerignore
node_modules
npm-debug.log
.git
.env.local
.DS_Store
Dockerfile*
docker-compose*
README.md
.github
coverage
dist
💡 .dockerignore의 중요성 💡 Importance of .dockerignore
.dockerignore가 없으면 node_modules가 빌드 컨텍스트에 포함되어 빌드 속도가 느려집니다. 또한 .git 폴더도 이미지에 들어가 크기가 불필요하게 커집니다.
Without .dockerignore, node_modules is included in the build context, slowing down builds. Also, .git folder gets into the image, unnecessarily increasing size.
이미지 크기 분석 Image Size Analysis
$ docker history my-app:latest
# 상세 분석 (dive 도구)
$ dive my-app:latest
$ docker history my-app:latest
# Detailed analysis (dive tool)
$ dive my-app:latest
