배포 속도와 인프라 효율성은 현대 웹 서비스의 핵심 경쟁력이 되었습니다. 특히 대규모 트래픽을 처리하거나 빠른 업데이트가 중요한 서비스라면, 이 두 요소가 서비스의 성패를 가르는 결정적 요인이 되기도 합니다. 하지만 현장에서는 프론트엔드 개발자와 인프라 개발자 사이의 지식 격차로 인해 최적화의 기회를 놓치는 경우가 많습니다.
여러분은 Next.js 프로젝트를 Docker로 배포할 때 이미지 크기가 예상보다 크게 나와 고민해본 적 있으신가요? 또는 배포 시간이 너무 오래 걸려 답답함을 느끼신 적이 있으신가요? 오늘은 이런 문제를 해결할 수 있는 두 가지 핵심 전략, Next.js의 ‘standalone 모드’와 Docker의 ‘멀티스테이지 빌드’에 대해 알아보겠습니다.
standalone 모드로 이미지 크기 78% 감소하기
Next.js 프로젝트는 기본적으로 많은 파일과 의존성을 포함하고 있어 Docker 이미지 크기가 불필요하게 커지기 쉽습니다. 이런 상황에서 Next.js의 ‘standalone 모드’는 마치 숨겨진 보석과 같은 존재입니다. 단 한 줄의 설정 변경만으로 웹 애플리케이션 실행에 꼭 필요한 최소한의 파일만 남겨 Docker 이미지 크기를 획기적으로 줄일 수 있습니다.
standalone 모드란 무엇인가?
standalone 모드는 Next.js 프로젝트에서 `next.config.js` 파일에 `output: ‘standalone’` 옵션을 추가하는 것만으로 활성화됩니다. 이 모드가 활성화되면 Next.js는 애플리케이션 실행에 필요한 최소한의 코드만 추출해 `.next/standalone` 폴더에 모아줍니다.
// next.config.js
module.exports = {
output: 'standalone'
}
standalone 모드의 놀라운 최적화 방식
1. 불필요한 파일 제거
일반적인 Docker 빌드에서는 `node_modules`를 비롯한 프로젝트의 모든 파일을 컨테이너에 복사합니다. 이로 인해 `pages/`, `app/`, 테스트 코드, `devDependencies` 같은 요소들이 이미지에 모두 포함되어 Docker 이미지 크기가 커집니다. standalone 모드는 운영 환경에서 애플리케이션 실행에 반드시 필요한 파일만 포함한 최소한의 환경으로 정리합니다.
2. node_modules 최적화
standalone 모드를 활성화하면 `node_modules` 역시 실행에 꼭 필요한 패키지만 선택적으로 포함합니다. 개발 과정에서 사용된 불필요한 패키지(devDependencies, ESLint, TypeScript 등)는 자동으로 제외되고, Next.js 서버 동작에 필요한 패키지만 남깁니다.
78.9%의 이미지 크기 감소
실제로 최근 작업 중인 standalone 빌드를 적용하지 않은 기본 Next.js 프로젝트의 Docker 이미지 크기는 986.95MB였습니다. 그러나 단순히 Next.js 설정에서 standalone 옵션만 추가했을 뿐인데, Docker 이미지 크기가 208MB로 줄어들었습니다. 이는 무려 78.9%의 용량 감소 효과로, 배포 속도와 인프라 효율성에 극적인 개선을 가져왔습니다.
standalone 모드에 최적화된 Dockerfile
standalone을 올바르게 적용하려면 Dockerfile 역시 함께 수정해야 합니다. standalone 옵션을 활성화하면 Next.js가 실행에 꼭 필요한 최소한의 파일만 `.next/standalone` 폴더로 정리해주는데, Dockerfile도 이 최적화된 결과물만 복사하도록 변경해야 합니다.
# 최적화된 Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
FROM node:18-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
EXPOSE 3000
CMD ["node", "server.js"]
이처럼 효율적인 standalone 모드는 Vercel에서도 공식 권장하는 최적화 방식으로, 배포 속도 개선과 인프라 비용 절감은 물론 컨테이너 구동 속도 향상, 보안 강화까지 다양한 효과를 제공합니다.
Docker 멀티스테이지 빌드: 개발 환경과 운영 환경의 명확한 분리
Next.js의 standalone 빌드와 함께 반드시 고려해야 할 또 다른 최적화 방법은 Docker 멀티스테이지 빌드입니다. 멀티스테이지 빌드를 활용하면 빌드 환경과 실행 환경을 명확히 분리해 최종 이미지를 더욱 가볍고 안전하게 만들 수 있습니다.
멀티스테이지 빌드란?
멀티스테이지 빌드는 하나의 Dockerfile 안에서 여러 단계(stage)를 사용해 빌드 환경과 실행 환경을 분리하는 기술입니다. 일반적인 Docker 빌드에서는 빌드 도구, 소스 코드, 캐시 파일 같은 실행에 불필요한 요소까지 모두 최종 이미지에 포함되어 이미지 크기가 커지고 보안상 취약점이 발생할 수 있습니다. 멀티스테이지 빌드는 빌드 단계에서 생성된 결과물 중 실제 런타임에 꼭 필요한 파일만 최종 이미지로 전달합니다.
멀티스테이지 빌드의 구성
멀티스테이지 빌드는 크게 두 가지 단계로 구성됩니다:
1. 빌드 스테이지(Builder Stage)
소스 코드를 컴파일하고 빌드하는 단계로, 개발 도구, 컴파일러, 빌드 의존성 등 빌드에 필요한 모든 요소가 포함됩니다.
2. 런타임 스테이지(Runtime Stage)
실제 애플리케이션을 실행하는 단계로, 빌드 스테이지에서 만들어진 결과물 가운데 실행에 꼭 필요한 파일만 복사해 사용합니다.
멀티스테이지 빌드의 놀라운 3가지 이점
1. 이미지 크기 최소화
멀티스테이지 빌드의 가장 큰 장점은 최종 런타임 이미지 크기를 대폭 줄일 수 있다는 점입니다. 실제 예시에서는 멀티스테이지 빌드만으로도 Docker 이미지 크기가 345MB 감소(약 28.5% 용량 절감)하는 효과를 보였습니다.
2. 빌드 단계와 실행 단계의 명확한 분리
빌드 환경과 런타임 환경이 명확히 분리되어 각 단계별로 최적화된 환경을 유지할 수 있습니다. 이는 빌드 도구와 런타임 환경 간의 버전 충돌 같은 문제를 사전에 방지하는 효과가 있습니다.
3. 효율적인 캐시 전략 구현
멀티스테이지 빌드는 Docker 레이어 캐시(layer cache)를 효과적으로 활용할 구조를 제공합니다. 예를 들어, `package.json`과 `package-lock.json`을 먼저 복사한 다음 `npm install`을 실행하는 전략을 구성하면 의존성 파일에 변경이 없을 때 캐시된 결과를 재사용해 빌드 속도를 크게 향상시킬 수 있습니다.
GitHub Actions에서의 Docker 캐시 전략
GitHub Actions 환경에서는 매번 새로운 클린 환경에서 워크플로(Workflow)가 실행됩니다. 이는 보안성과 안정성 측면에서는 강점이지만, Docker 레이어 캐시가 유지되지 않아 같은 코드를 반복해 빌드하더라도 매번 모든 과정을 처음부터 다시 실행해야 하는 한계가 있습니다.
이 문제를 해결하기 위해 GitHub Actions 환경에서는 Docker Buildx와 actions/cache를 조합한 캐시 전략이 효과적입니다. 멀티스테이지 빌드와 함께 사용하면 의존성 설치 단계와 애플리케이션 빌드 단계를 명확히 구분하여 Docker 캐시를 더욱 효율적으로 활용할 수 있습니다.
GitHub Actions에서 Docker 캐시 전략의 이점
- 의존성 설치와 빌드 단계의 명확한 분리로 캐시 최적화가 용이합니다.
- 최종 이미지는 빌드 산출물만 포함하여 이미지 크기를 효과적으로 줄일 수 있습니다.
- Docker Buildx를 사용하면 각 스테이지별로 캐시를 독립적으로 관리할 수 있습니다.
- CI/CD 파이프라인 내에서 캐시 재사용 전략을 명확하고 효율적으로 구성할 수 있습니다.
standalone과 멀티스테이지 빌드: 함께 사용할 때 극대화되는 시너지
두 방식 모두 Docker 배포 환경을 최적화하는 것을 목표로 하지만, 각각 적용할 위치와 역할이 다릅니다.
명확한 역할 구분
- standalone 모드: Next.js 프로젝트 자체를 최적화하는 기능으로, 실행에 필수적인 파일과 의존성만 남기고 불필요한 코드와 의존성을 제거합니다.
- 멀티스테이지 빌드: Docker의 관점에서 전체 애플리케이션 환경을 최적화하는 방법으로, 빌드 환경과 실행 환경을 명확히 분리하여 최종 이미지에 불필요한 요소를 제거합니다.
최적의 조합: 두 기술의 시너지 효과
Next.js의 standalone 모드와 Docker의 멀티스테이지 빌드를 함께 활용하는 것이 가장 효과적인 최적화 전략입니다. standalone 모드로 애플리케이션 실행에 필요한 최소한의 파일만 포함한 빌드 결과물을 생성하고, 멀티스테이지 빌드로 빌드와 실행 환경을 명확히 분리하여 최종 이미지에서 불필요한 요소를 완벽하게 제거합니다.
경계를 넘어, 최적의 배포 환경으로 향하는 여정
현대 웹 서비스에서 배포 속도와 인프라 효율성은 경쟁력의 핵심 요소입니다. 더 빠른 배포, 더 안정적인 서비스, 그리고 더 낮은 인프라 비용을 실현하려면 기존 개발 환경의 비효율을 제거하고 꾸준한 최적화가 필요합니다.
프론트엔드 개발자가 Docker와 멀티스테이지 빌드를 이해하고, 인프라 개발자가 Next.js standalone 모드를 깊이 있게 활용할 때, 경계를 넘어 더욱 완성도 높은 배포 시스템이 탄생합니다. 단순한 설정 하나, 사소해 보이는 파일 최적화 하나가 팀 전체의 배포 효율성을 높이고, 인프라 비용 절감과 서비스 전체의 안정성 향상으로 이어집니다.
여러분의 프로젝트는 이러한 최적화 기법을 적용하고 있나요? 지금 바로 팀의 빌드 파이프라인과 배포 프로세스를 점검하고, Next.js standalone 모드와 Docker 멀티스테이지 빌드, 그리고 효과적인 캐시 전략의 조합으로 서비스를 한 단계 더 높은 수준으로 끌어올리는 여정을 시작해 보세요.