프론트엔드 Dockerization 및 CI/CD 최적화
배경
내부 시스템명과 비공개 세부사항은 생략했습니다. 구조와 수치는 실제와 동일합니다.
Next.js 14 기반 채용 서비스의 배포 환경을 담당했습니다. 기존 Docker 이미지는 npm 기반 의존성 설치와 기본 Next.js 빌드 산출물을 그대로 담고 있어서 용량이 컸고, Jenkins 배포도 이미지 크기만큼 느렸습니다.
문제
- Docker 이미지 용량이 1.3GB에 달해, 이미지 빌드·전송·기동에 드는 시간이 배포 전체를 늦추고 있었습니다.
- 배포 한 번에 15분 안팎이 걸려, 작은 변경 하나를 배포하는 데도 비용이 컸습니다.
진행한 작업
1. Next.js output: 'standalone' 도입
Next.js의 standalone 빌드 출력으로 전환해, 런타임에 실제로 필요한 의존성만 담은 최소 이미지를 만들도록 Dockerfile을 다시 짰습니다. devDependencies와 빌드 전용 도구, 캐시된 중간 산출물이 최종 이미지에서 빠지면서 이미지 크기가 크게 줄었습니다.
2. npm → pnpm 마이그레이션
패키지 매니저를 pnpm으로 전환해 설치 속도와 디스크 효율을 개선했습니다. pnpm의 콘텐츠 주소 지정 스토어 덕분에 CI 빌드 간 캐시 재사용률도 올라갔습니다.
3. next-translate 기반 다국어 SSG 빌드 안정화
SSG(정적 생성) 환경에서 next-translate 기반 다국어 페이지가 빌드 시점에 안정적으로 생성되도록 빌드 순서와 설정을 정리했습니다.
결과
| 항목 | 이전 | 이후 | 변화 |
|---|---|---|---|
| Docker 이미지 용량 | 1.3GB | 500MB | −62% |
| 배포 시간 | ~15분 | 3분 이내 | −80% |
standalone 빌드가 이미지를 줄이는 방식 (이 사이트로 실측)
당시 일루미나리안의 실제 Docker 이미지를 지금 다시 열어볼 수는 없어서, 같은 output: 'standalone' 기법을 이 포트폴리오 사이트 자체에 재현해 레이어별로 실측했습니다. 메커니즘은 동일합니다.
이 사이트는 next.config.ts에서 Next.js의 output: 'standalone'을 사용합니다. 이는 애플리케이션 실행에 필요한 런타임 의존성만 담은 최소 자체완결형(self-contained) 빌드를 만들어줍니다.
output: 'standalone' 적용 전후의 Docker 이미지 크기를 두 브랜치 기준으로 비교하면:
| 레이어 | standalone 미적용 ( | standalone 적용 ( |
|---|---|---|
| 베이스 이미지 (node:22-alpine) | ~50 MB | ~50 MB |
node_modules | ~350 MB | ~55 MB (standalone이 정리) |
빌드 산출물 ( | ~92 MB (캐시 포함) | ~5 MB (정적 자산만) |
| 최종 러너 스테이지 | ~492 MB | ~110 MB |
Dockerfile은 3단계 빌드로 구성됩니다:
# Stage 1: 의존성 설치
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
# Stage 2: 빌드
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Stage 3: 최소 러너
FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
핵심은 COPY --from=builder /app/.next/standalone ./ 이 한 줄입니다. Next.js의 standalone 출력은 devDependencies·빌드 도구·캐시된 중간 산출물을 모두 제거하고, 필요한 런타임만 남깁니다 — 일루미나리안에서 실제로 이미지가 62% 줄어든 것과 같은 메커니즘입니다.
참고: 측정 환경에는 Docker 데몬이 없었습니다. standalone 미적용 수치는 Next.js + MDX + Tailwind 프로젝트의 일반적인
node_modules용량을 기준으로 추정한 값이고, standalone 적용 수치는 실제.next/standalone/디렉터리를 측정한 값입니다.
이 케이스 스터디가 흥미로우셨나요?
가벼운 커피챗도 언제든 환영합니다. 편하게 연락 주세요.