요즘 웹 서비스 환경에서 빠른 배포와 효율적인 인프라 관리는 더 이상 ‘있으면 좋은’ 요소가 아닌 ‘반드시 필요한’ 요소가 되었습니다. 특히 대규모 트래픽을 처리하거나 빠른 업데이트가 중요한 서비스라면, 배포 속도와 인프라 효율성은 서비스의 경쟁력을 좌우하는 핵심이 됩니다. 하지만 실제 현장에서는 프론트엔드 개발자와 인프라 개발자 사이 지식 격차가 존재하는 경우가 많습니다. 프론트엔드 개발자는 Docker의 멀티스테이지(Multi-stage) 빌드나 CI/CD 캐싱(Caching) 전략에 익숙하지 않은 편입니다. 반면 인프라 개발자는 백엔드(Backend) 환경에는 비교적 익숙하지만, Next.js의 standalone 빌드와 같은 프론트엔드 배포 최적화 기법이 다소 생소할 수 있습니다. 이번 글에서는 이러한 두 영역 간의 간극을 좁히고, 배포 속도와 인프라 비용 모두를 최적화할 수 있는 방법을 소개하려고 합니다. <출처: unsplash> Next.js standalone 모드로 Docker 빌드 최적화하기프론트엔드 프로젝트에서는 빌드 후 파일 용량이 예상보다 커지는 경우가 흔합니다. 특히 Next.js 프로젝트는 기본적으로 많은 파일과 의존성을 담고 있기 때문에 Docker 이미지 크기도 불필요하게 커지기 쉽습니다. 이때 Next.js의 ‘standalone 모드’를 활용하면, 웹 애플리케이션 실행에 꼭 필요한 최소한의 파일로 경량화된 빌드 결과물을 만들 수 있습니다. 이렇게 Docker 이미지 크기를 획기적으로 줄이고, 배포 속도와 인프라 효율성을 크게 높일 수 있습니다. Next.js standalone이란?Next.js의 standalone 모드는 설정에서 output: ‘standalone’를 추가하는 것만으로 활성화됩니다. 해당 옵션을 적용하면 Next.js 프로젝트에서 독립 실행형(standalone)의 빌드 결과물이 생성되는데요. 이때 애플리케이션 실행에 필요한 최소한의 코드만 별도로 추출해 /standalone 폴더에 모아 줍니다. standalone은 어떻게 최적화할까? 1. 불필요한 파일 제거일반적인 Docker 빌드에서는 node_modules를 비롯한 프로젝트의 모든 파일을 컨테이너에 그대로 복사합니다. 이 때문에 pages/, app/, 테스트 코드, devDependencies 같은 요소들이 이미지에 모두 포함되어 Docker 이미지 크기가 커집니다. 그러나 standalone 모드를 사용하면 운영 환경에서 애플리케이션 실행에 반드시 필요한 파일만 포함한, 최소한의 환경으로 정리할 수 있습니다. 2. node_modules 최적화또한 standalone 모드를 활성화하면, node_modules 역시 실행에 꼭 필요한 패키지만 선택적으로 포함합니다. 개발 과정에서 쓰였던 불필요한 패키지(devDependencies, ESLint, TypeScript 등)는 자동으로 제외됩니다. 즉, Next.js 서버 동작에 필요한 패키지만 남기고 나머지는 모두 제거합니다. 최적화 과정을 거친 결과는 이렇습니다. Docker 이미지 크기를 크게 줄일 수 있습니다.AWS ECR이나 Docker Hub에 이미지를 업로드할 때, 전송 속도가 빨라지며 배포 시간이 단축됩니다.컨테이너 시작 시간이 빨라지고, 인프라 리소스도 효율적으로 사용할 수 있습니다. 예시로 알아보는 Next.js standalone설정 방법은 간단합니다. Next.js의 next.config.js 파일에 아래와 같이 입력합니다. <출처: 작가> 설정을 적용하고 next build 명령어를 실행하면 .next/standalone 디렉터리가 만들어집니다. 폴더는 실행에 필요한 코드만 포함하고 있으며, 이후 Docker 빌드에 활용할 수 있습니다. 예시 Dockerfile 이미지 용량 비교아래는 standalone 빌드를 적용하지 않은 기본 Dockerfile입니다. npx create-next-app@latest 명령어로 생성한 기본 보일러플레이트 프로젝트를 빌드한 결과입니다. <출처: 작가> 이렇게 만든 Docker 이미지 크기는 986.95MB로 나옵니다. 아직 최적화를 하지 않은 상태로, 전체 프로젝트 폴더와 모든 node_modules가 들어 있어 무겁습니다. 아래는 standalone 빌드를 적용한 Dockerfile의 결과입니다. <출처: 작가> 단순히 Next.js 설정에서 standalone 옵션만 추가했을 뿐인데, Docker 이미지 크기가 208MB로 줄어들었습니다. 무려 78.9%나 용량이 감소한, 놀라운 효과입니다. standalone을 올바르게 적용하려면 Dockerfile 역시 함께 수정해야 합니다. standalone 옵션을 활성화하면, Next.js가 실행에 꼭 필요한 최소한의 파일만 .next/standalone 폴더로 정리해주는데요. 이때 Dockerfile이 그 최적화 결과물을 정확히 복사하지 않고, 기존 방식대로 전체 프로젝트나 불필요한 파일까지 포함하면, 이미지 크기가 다시 불필요하게 커지기 때문입니다. 이런 이유로 Dockerfile 역시 standalone 빌드로 만든 최적화된 결과물만 복사하도록 수정해야 합니다. 따라서 전체 프로젝트를 복사하는 대신 standalone 빌드 결과물만 복사하도록 Dockerfile에 아래와 같은 스크립트를 추가합니다. <출처: 작가> 이처럼 효율적인 standalone 모드는 Vercel에서도 공식 권장하는 최적화 방식입니다. 단순한 설정 변경만으로도 배포 속도 개선과 인프라 비용 절감은 물론, 컨테이너 구동 속도 향상, 보안 강화까지 다양한 효과를 기대할 수 있습니다. Docker 멀티스테이지 빌드로 최적화하기Next.js의 standalone 빌드와 함께 반드시 고려해야 할 것이 바로 Docker 멀티스테이지 빌드입니다. 멀티스테이지 빌드를 활용하면 빌드 환경과 실행 환경을 명확히 분리해, 최종 이미지를 더욱 가볍고 안전하게 만들 수 있는데요. 이는 배포 속도 향상과 인프라 비용 절감으로 이어지는 효과적인 최적화 방법입니다. Docker 멀티스테이지 빌드란?멀티스테이지 빌드(Multi-stage Build)는 하나의 Dockerfile 안에서 여러 단계(stage)를 사용해 빌드 환경과 실행 환경을 분리하는 기술입니다. 보통의 Docker 빌드에서는 빌드 도구, 소스 코드, 캐시 파일 같은 실행에 불필요한 요소까지 모두 최종 이미지에 포함되어, 이미지 크기가 커지고 보안상 취약점이 발생할 수 있습니다. 반면 멀티스테이지 빌드를 활용하면 빌드 단계에서 생성된 결과물 중 실제 런타임에 꼭 필요한 파일만 최종 이미지로 전달합니다. 멀티스테이지 빌드는 어떻게 최적화할까?멀티스테이지 빌드는 크게 아래 두 가지 단계로 구성합니다. 1. 빌드 스테이지(Builder Stage):소스 코드를 컴파일하고 빌드하는 단계입니다. 개발 도구, 컴파일러, 빌드 의존성 등 빌드에 필요한 모든 요소가 이 단계에 포함됩니다. 2. 런타임 스테이지(Runtime Stage):실제 애플리케이션을 실행하는 단계입니다. 빌드 스테이지에서 만들어진 결과물 가운데 실행에 꼭 필요한 파일만 복사해 사용합니다. 이런 방식으로 최종 Docker 이미지는 실행에 필요한 최소한의 파일만 포함합니다. 그 결과 이미지의 크기가 크게 줄어듭니다. 예시로 알아보는 멀티스테이지 빌드<출처: 작가> 멀티스테이지 빌드의 3가지 이점 1. 이미지 크기 최소화이 빌드의 가장 큰 장점은 최종 런타임 이미지 크기를 대폭 줄일 수 있다는 점 입니다. 빌드에 사용했던 도구, 소스 코드, 불필요한 라이브러리 없이 오직 실행에 필요한 파일만 포함됩니다. 예시 Dockerfile 이미지 용량 비교아래 이미지는 멀티스테이지 빌드를 적용하지 않은 Dockerfile입니다. <출처: 작가> 이어 멀티스테이지 빌드를 적용한 Dockerfile을 보겠습니다. <출처: 작가> standalone 옵션 없이도 멀티스테이지 빌드만으로, 345MB가 줄어들었습니다. 대략 28.5% 정도 용량 절감 효과를 볼 수 있겠네요. 2. 빌드 단계와 실행 단계를 분리앞서 보았듯 빌드 스테이지에서는 개발 도구와 빌드 환경이 포함됩니다. 반면 런타임 스테이지에서는 오직 애플리케이션 실행에 꼭 필요한 파일만 들어가기 때문에, 최종 이미지 크기가 훨씬 작고 가벼워집니다. 또한, 빌드 환경과 런타임 환경이 명확히 분리되어 각 단계별로 최적화 환경을 유지할 수 있다는 장점도 있습니다. 빌드 도구와 런타임 환경 간의 버전 충돌 같은 문제를 사전에 방지할 수 있다는 뜻입니다. 3. 효율적인 캐시 전략 구현그뿐만 아니라 멀티스테이지 빌드는 Docker 레이어 캐시(layer cache)를 효과적으로 활용할 구조를 제공합니다. 예를 들어, package.json과 package-lock.json을 먼저 복사한 다음, npm install을 실행하는 전략을 구성하면 이런 이점을 얻을 수 있습니다. 의존성 파일(package.json, package-lock.json 등)에 변경이 없다면 npm install 결과가 캐시에 저장되므로, 불필요한 재설치를 방지합니다.소스 코드만 변경된 경우에는 의존성 설치 과정을 건너뛰기 때문에 빌드 속도가 크게 올라갑니다.CI/CD 파이프라인에서도 전체적인 배포 시간이 짧아지는 효과를 기대할 수 있습니다. 이처럼 캐시된 레이어를 적절히 활용하는 전략은 속도와 리소스 효율성 측면에서 체감할 수 있는 큰 차이를 만들어냅니다. 즉, 멀티스테이지 빌드는 Next.js 프로젝트뿐만 아니라 대부분 Docker 기반 프로젝트에서 꼭 도입해야 할 최적화 전략입니다. 이미지 크기 절감과 배포 속도 개선은 물론이고, 보안과 인프라 효율까지 챙길 수 있으니까요. GitHub Actions 환경에서의 Docker 캐시 전략그렇다면, GitHub Actions 환경에서 Docker 캐시는 어디에 저장되며, 이를 효과적으로 활용할 방법은 무엇일까요? 또한 멀티스테이지 빌드와 Docker 캐시 간의 관계는 어떻게 정리할까요? GitHub Actions는 매번 새로운 클린 환경에서 워크플로(Workflow)를 실행합니다. 보안성과 안정성 측면에서는 분명한 강점이지만, Docker 레이어 캐시(layer cache)가 유지되지 않는다는 한계도 있습니다. 즉, 같은 코드를 반복해 빌드하더라도 매번 모든 과정을 처음부터 다시 실행하므로, 빌드 시간이 길어지고 자원이 낭비될 수 있습니다. 이러한 문제를 해결하기 위해 GitHub Actions 환경에서는 Docker Buildx와 actions/cache를 조합한 캐시 전략이 널리 쓰입니다. 또한, 멀티스테이지 빌드를 사용하면 의존성 설치 단계와 애플리케이션 빌드 단계를 명확히 구분할 수 있습니다. 덕분에 Docker 캐시를 더욱 효율적으로 활용할 수 있는 구조를 제공합니다. 멀티스테이지 빌드가 제공하는 주요 이점은 다음과 같습니다. 의존성 설치와 빌드 단계를 명확히 분리해 캐시 최적화가 더욱 쉬워집니다.최종 이미지는 빌드 산출물만 포함하므로 이미지 크기를 효과적으로 줄일 수 있습니다.Docker Buildx를 사용하면 각 스테이지(stage) 별로 캐시를 독립적으로 관리할 수 있습니다.CI/CD 파이프라인 내에서 캐시 재사용 전략을 더욱 명확하고 효율적으로 구성할 수 있습니다. 이처럼 GitHub Actions에서의 Docker 캐시는 기본적으로 휘발성 환경의 약점을 보완하는 역할을 합니다. actions/cache와 Docker Buildx를 적절히 조합하고, 멀티 스테이지 빌드를 함께 적용하면 이미지를 최적화하며 빌드 시간을 크게 줄일 수 있습니다. CD/CD 파이프라인의 안정성과 속도를 올릴 효율적인 전략을 찾는다면, 멀티스테이지 빌드와 캐시 관리 기법의 도입을 적극 추천합니다. standalone과 멀티스테이지 빌드의 차이그렇다면 둘 중 하나만 선택해 적용하면 되는 걸까요? 두 방식 모두 Docker 배포 환경을 최적화하여, 최종 산출물을 가볍고 효율적으로 만드는 것을 목표로 한다는 공통점이 있습니다. 그러나 각각 적용할 위치와 역할이 다릅니다. Next.js는 기본적으로 빌드 결과가 크고, 모든 node_modules를 포함하는 구조입니다. 하지만 standalone 옵션을 활성화하면, 실행에 꼭 필요한 최소한의 파일만 .next/standalone 폴더에 정리됩니다. 즉, standalone 모드는 Docker가 아닌 Next.js 프로젝트 자체를 최적화하는 기능입니다. 다시 말해, 실행에 필수적인 파일과 의존성만 남기고, Next.js 서버와 직접 관련이 없는 불필요한 코드와 의존성(devDependencies, 테스트 파일 등)을 모두 제거하는 일을 수행합니다. 반면 멀티스테이지 빌드는 Docker의 관점에서 전체 애플리케이션 환경을 최적화하는 방법입니다. 빌드 단계에서는 TypeScript, ESLint, webpack 등 개발용 도구를 쓰고, 런타임 단계에서는 빌드 결과물을 제외한 개발 도구, 소스 코드, 빌드 전용 파일을 모두 제거하는 방식이죠. 실행 환경에서는 오직 빌드 결과물만을 새로운 베이스 이미지로 복사하는 것입니다. 그렇게 빌드 전용 파일이나 도구 등 불필요한 요소가 없는 최적의 이미지를 생성합니다. 따라서 Next.js의 standalone 모드와 Docker의 멀티스테이지 빌드를 함께 활용하는 것이 가장 효과적인 최적화 전략입니다. 우선 standalone 모드로 애플리케이션 실행에 필요한 최소한의 파일과 의존성만을 포함한 빌드 결과물을 생성합니다. 곧이어 멀티스테이지 빌드로 빌드와 실행 환경을 명확히 분리해 최종 이미지에서 불필요한 파일과 도구를 완벽하게 제거할 수 있습니다. 마치며: 경계를 넘어, 최적의 배포 환경으로 가는 법현대 웹 서비스에서 배포 속도와 인프라 효율성은 경쟁력의 핵심 요소입니다. 그런 만큼 더 빠른 배포, 더 안정적인 서비스, 그리고 더 낮은 인프라 비용을 실현하려면 기존 개발 환경의 비효율을 제거하고 최적화를 꾸준히 고민해야 합니다. 프론트엔드 개발자가 Docker와 멀티스테이지 빌드를 이해하고, 인프라 개발자가 Next.js standalone 모드를 깊이 있게 활용할 때, 경계를 넘어 더욱 완성도 높은 배포 시스템이 탄생합니다. 단순한 설정 하나, 사소해 보이는 파일 최적화 하나가 팀 전체의 배포 효율성을 높이는 것입니다. 이 작은 변화는 인프라 비용 절감과 서비스 전체의 안정성을 높일 큰 변화로 이어질 수 있습니다. 지금 바로 팀의 빌드 파이프라인과 배포 프로세스를 점검하고, 더 나은 속도와 안정성을 위한 최적화를 시작해 보는 건 어떨까요? Next.js standalone 모드와 Docker 멀티스테이지 빌드, 그리고 효과적인 캐시 전략의 조합이 여러분의 서비스를 한 단계 더 높은 수준으로 끌어올릴 것입니다. ©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.