

Next.js standalone으로 Docker 이미지 줄이기
Next.js 애플리케이션을 Docker로 배포할 때 이미지가 예상보다 커지는 경우가 많아요. 소스 코드, 개발 의존성, 전체 node_modules, 빌드 캐시가 최종 이미지에 함께 섞이면 배포 크기와 보안 부담이 같이 늘어나요.
Next.js의 output: "standalone"은 이 문제를 줄이기 위한 빌드 옵션이예요. 프로덕션 실행에 필요한 파일만 추적해 .next/standalone 폴더에 모아주고, 그 안에는 최소한의 node_modules까지 함께 들어가요.
standalone이 하는 일
Next.js 공식 문서는 output: "standalone"을 설정하면 node_modules 설치 없이 단독으로 배포할 수 있는 .next/standalone 폴더와, next start를 대신할 수 있는 최소 server.js가 생성된다고 설명해요.
const nextConfig = {
output: "standalone",
};
export default nextConfig;
빌드 후에는 .next/standalone 안에 프로덕션 서버를 실행하기 위한 파일이 들어가요. 그래서 배포 환경에서는 전체 프로젝트를 복사하지 않고 이 폴더를 중심으로 이미지를 구성할 수 있어요.
node .next/standalone/server.js
중요한 점은 next start를 쓰는 방식과 다르다는 거예요. standalone은 빌드 과정에서 추적된 최소 서버를 직접 실행하는 방식에 가까워요. 실제로 standalone 빌드 후에는 next CLI에 필요한 의존성이 남지 않기 때문에 next start를 그대로 실행하면 동작하지 않아요.
public과 static은 따로 챙겨야 해요
standalone에서 자주 놓치는 부분은 정적 자산이예요. Next.js 문서는 public과 .next/static이 standalone 폴더에 자동으로 복사되지 않으며, 필요하다면 따로 복사해야 한다고 설명해요.
이 동작은 빠뜨린 것이 아니라 의도된 설계예요. Next.js는 정적 자산을 standalone 서버가 직접 서빙하기보다 CDN이 처리하는 편이 이상적이라고 보기 때문에, 최소 서버는 이 폴더들을 기본적으로 포함하지 않아요. 따라서 앞단에 CDN을 두고 정적 자산을 그쪽에서 서빙하는 구성이라면 굳이 복사하지 않아도 돼요. 반대로 CDN 없이 standalone 서버가 정적 자산까지 직접 책임지는 구성이라면, 아래처럼 두 폴더를 직접 복사해야 server.js가 이를 서빙해요.
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/
Dockerfile에서도 같은 구조가 필요해요.
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
이 단계를 빼먹으면 애플리케이션은 뜨는데 이미지, CSS, JS 청크가 404로 깨질 수 있어요. standalone이 "모든 것을 알아서 넣어준다"는 오해 때문에 자주 생기는 문제예요.
멀티 스테이지 빌드와 함께 써야 효과가 큽니다
standalone 자체만으로도 필요한 파일을 줄일 수 있지만, Docker에서는 멀티 스테이지 빌드와 함께 사용할 때 효과가 커요.
빌드 단계에서는 전체 의존성과 소스 코드가 필요해요. 하지만 실행 단계에는 빌드 결과와 런타임에 필요한 파일만 있으면 돼요.
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable pnpm && pnpm build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]
실행 이미지에는 빌드 도구와 소스 전체가 남지 않아요. 이 구조가 이미지 크기, 배포 속도, 취약점 표면을 함께 줄여요.
실행 단계에서 node server.js로 시작하는 것은 standalone 폴더를 작업 디렉터리 루트(./)로 복사했기 때문이예요. 로컬에서는 node .next/standalone/server.js로 실행하지만, Docker에서는 standalone 폴더 자체가 루트가 되므로 진입점이 server.js가 돼요.
Docker에서 흔히 겪는 함정: HOSTNAME
standalone 서버를 컨테이너에 띄웠는데 외부에서 접속이 안 되는 경우가 많아요. 대부분 호스트명 바인딩이 원인이예요.
server.js는 포트와 호스트명을 환경 변수로 받아요. HOSTNAME을 지정하지 않으면 컨테이너 내부 주소에만 바인딩되어, 호스트나 다른 컨테이너에서 접근하지 못할 수 있어요. 그래서 Docker 환경에서는 HOSTNAME=0.0.0.0을 명시해 모든 인터페이스에서 수신하도록 해주는 것이 안전해요.
PORT=3000 HOSTNAME=0.0.0.0 node server.js
위 Dockerfile처럼 ENV로 미리 지정해두면 실행 시 따로 신경 쓰지 않아도 돼요. 애플리케이션은 정상적으로 뜨는데 접속만 되지 않는다면, 먼저 이 부분을 확인해볼 만해요.
standalone이 맞지 않는 경우
standalone은 서버 실행이 필요한 Next.js 애플리케이션에 적합해요. 반대로 정적 export만 필요한 사이트라면 output: "export"나 정적 호스팅 구성이 더 맞을 수 있어요.
또한 monorepo에서는 파일 추적 범위를 확인해야 해요. Next.js의 output file tracing은 필요한 파일을 추적하지만, 워크스페이스에서 가져오는 패키지나 외부 패키지 참조 방식에 따라 추가 설정이 필요할 수 있어요. 런타임에 동적으로 import하는 코드처럼 정적으로 추적되지 않는 부분은 번들에서 누락될 수 있다는 점도 함께 고려해야 해요.
즉, standalone은 "Docker 배포를 쉽게 해주는 옵션"이지 모든 배포 환경의 정답은 아니예요.
확인해야 할 체크리스트
Next.js standalone 배포에서 최소한 아래 항목을 확인해야 해요.
next.config에output: "standalone"이 설정되어 있는가?- 실행 단계에서
.next/standalone을 복사했는가? - (CDN을 쓰지 않는다면)
public과.next/static을 따로 복사했는가? - 컨테이너 명령이
node server.js를 실행하는가? - 런타임 포트와
HOSTNAME=0.0.0.0이 배포 환경에 맞게 지정되어 있는가? - 이미지 안에 불필요한 개발 의존성과 소스가 남지 않았는가?
정리
Next.js standalone은 프로덕션 실행에 필요한 파일만 모아 Docker 이미지를 작게 만드는 데 유용해요. 하지만 정적 자산까지 자동으로 완성해주는 마법은 아니예요.
Docker에서 standalone을 쓸 때 핵심은 네 가지예요. 빌드와 실행 단계를 분리하고, standalone 폴더를 실행 이미지로 가져오고, 필요하다면 public과 .next/static을 따로 복사하고, HOSTNAME을 포함한 런타임 환경 변수를 배포 환경에 맞게 지정하는 거예요.
배포 이미지를 줄이고 싶다면 단순히 베이스 이미지를 alpine으로 바꾸는 것보다, 먼저 최종 이미지에 무엇이 들어가야 하는지부터 좁혀야 해요.
참고
- Next.js Docs — output
- Docker Docs — Containerize a Next.js application
참고
Read more

블록체인 자금 추적에서 Pari Passu는 어떻게 쓰이나
블록체인 거래 그래프에서 자금이 섞였을 때 Pari Passu 방식이 어떤 의미로 쓰이는지, FIFO와 Rolling Charge와 비교해 실무적으로 확인해야 할 지점을 정리해요.

디자인 시스템이 필요한 이유는 컴포넌트가 많아서가 아니다
디자인 시스템을 단순한 UI 컴포넌트 모음이 아니라 제품의 반복 가능한 의사결정 구조로 봐야 하는 이유를 정리해요.

산세리프와 세리프, 무엇을 기준으로 골라야 할까
산세리프와 세리프의 차이를 단순한 취향 문제가 아니라 제품의 맥락, 화면 환경, 폴백 전략까지 포함한 타이포그래피 선택 기준으로 정리해요.