Docker로 Node.js 애플리케이션 컨테이너화 및 최적화
Node.js 애플리케이션을 Docker로 컨테이너화하면서 이미지 크기와 빌드 속도를 개선하는 핵심 원칙과 멀티스테이지 빌드 적용 사례를 정리한 기술자료
목차
소개
Node.js 애플리케이션을 Docker로 배포할 때는 단순히 이미지가 동작하는 것을 넘어서서 빌드 속도, 이미지 크기, 보안, 재현성 등을 고려해야 한다. 이 글은 초보자도 이해하기 쉽게 Dockerfile 작성법과 최적화 방법을 설명한다. 핵심 키워드는 "Node.js Dockerfile 최적화", "멀티스테이지 빌드 Node.js", "Docker 컨테이너 작은 이미지 만들기"다.
기본 원칙
효율적인 컨테이너 이미지는 다음 원칙을 따른다.
- 불필요한 파일 제외
- 레이어 수 최소화
- 빌드 캐시 활용
- 런타임에 필요한 파일만 포함
- 최소 권한 원칙 적용
프로젝트 준비
.dockerignore 설정
소스와 함께 전송할 필요 없는 파일을 제외하면 이미지 빌드가 빨라지고 크기가 줄어든다.
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.env
coverage
Dockerfile 작성 전략
Node.js 애플리케이션은 빌드 단계와 런타임 단계를 분리하는 멀티스테이지 빌드가 효과적이다. 빌드에 필요한 개발 의존성과 런타임 의존성을 분리하면 최종 이미지는 훨씬 가벼워진다.
멀티스테이지 예제
FROM node:18 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-slim AS run
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]
위 예제에서 첫 번째 스테이지는 빌드와 의존성 설치를 담당한다. 두 번째 스테이지는 런타임에 필요한 결과물만 복사해서 이미지 크기를 줄인다.
레이어 최적화
자주 변경되는 파일은 하단에 두어 캐시 활용을 최적화한다. 예를 들어 의존성 설치 명령은 package.json 복사 후 실행하고, 애플리케이션 소스는 그 다음에 복사한다.
이미지 크기 줄이기
이미지 크기를 줄이기 위한 방법은 여러 가지다.
- 베이스 이미지를 slim 또는 Alpine 계열로 선택
- 개발 도구가 포함된 파일 제거
- 정적 파일은 CDN 사용으로 이미지에서 제외
- 최종 단계에서 불필요한 캐시 파일이나 빌드 산출물 제거
- distroless 이미지를 검토하여 런타임 최소화
Alpine vs slim
Alpine은 작지만 일부 네이티브 모듈 빌드에 추가 설정이 필요하다. slim 계열은 호환성이 좋고 보안 패치가 자주 적용된다. 프로젝트 특성에 따라 선택한다.
의존성 관리
npm ci를 이용하면 package-lock.json에 명시된 정확한 버전으로 설치되어 빌드 재현성이 향상된다. 프로덕션 이미지에는 devDependencies를 포함하지 않는 것이 일반적이다.
캐시 전략과 빌드 속도
빌드 캐시를 효과적으로 사용하면 반복 빌드 시간이 크게 단축된다. CI 환경에서는 빌드 캐시를 레이어 단위로 재사용하거나 빌드킷(BuildKit)을 활용하는 방법을 고려한다.
보안과 권한
런타임 컨테이너는 루트가 아닌 비루트 사용자로 실행하는 것이 권장된다. 컨테이너 내부에서 최소 권한만 부여하고 불필요한 포트를 노출하지 않는다.
운영 고려사항
- 환경 변수는 민감 정보를 포함하지 않도록 비밀 관리를 별도 시스템으로 분리
- 헬스체크(HEALTHCHECK)를 이용한 상태 검증
- 로그는 표준 출력으로 보내서 중앙집중형 로깅과 연동
헬스체크 예시
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:3000/health || exit 1
실전 빌드 커맨드
docker build -t myapp:latest .
# 빌드킷 사용 예시
DOCKER_BUILDKIT=1 docker build --progress=plain -t myapp:latest .
요약
멀티스테이지 빌드를 통해 빌드와 런타임을 분리하면 이미지 크기와 보안이 개선된다. .dockerignore와 레이어 최적화로 빌드 속도를 높일 수 있다. 베이스 이미지 선택, 의존성 설치 방식, 비루트 사용자 적용 등 작은 설정들이 운영 효율에 큰 영향을 준다. 위 원칙을 바탕으로 프로젝트 요구사항에 맞는 최적화를 단계적으로 적용하면 안정적이고 가벼운 Node.js Docker 이미지 확보가 가능하다.