Contents
see List배포 이미지는 실행에 필요한 것만 남겨야 한다
Docker 이미지는 개발 환경을 빠르게 재현하게 해 주지만, Dockerfile을 그대로 쌓아 올리면 소스 코드, 패키지 캐시, 빌드 도구, 테스트 파일까지 운영 서버로 전달되기 쉽습니다. 이미지가 불필요하게 커지면 배포와 롤백 시간이 길어지고, 취약점 점검 대상 패키지도 늘어납니다. 운영 이미지는 애플리케이션을 실행하는 데 필요한 런타임과 산출물만 갖도록 설계하는 것이 기본입니다.
가장 효과적인 방법은 멀티 스테이지 빌드입니다. 첫 단계에서는 컴파일과 의존성 설치를 수행하고, 마지막 단계에서는 그 결과물만 복사합니다. 빌드 도구와 임시 캐시는 최종 이미지에 남지 않습니다. Node.js 서비스라면 npm, TypeScript 컴파일러, 테스트 도구를 빌더에 두고, 운영 단계에는 production 의존성과 dist 디렉터리만 넣는 방식이 실용적입니다.
Node.js 서비스용 멀티 스테이지 Dockerfile
다음 예시는 TypeScript 기반 Node.js 서비스를 기준으로 합니다. 의존성 파일를 소스보다 먼저 복사해 캐시 재사용률을 높이고, 빌드 단계와 실행 단계를 분리합니다. 실제 프로젝트의 시작 파일과 포트는 환경에 맞게 바꾸면 됩니다.
FROM node:22-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:22-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
RUN useradd --system --uid 10001 appuser
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]빌더에서 생성한 dist만 가져오므로 개발 의존성인 TypeScript, ESLint, 테스트 라이브러리는 런타임 이미지에 포함되지 않습니다. 다만 네이티브 모듈을 사용하는 프로젝트는 빌드 단계와 실행 단계의 운영체제 계열을 맞춰야 합니다. 예를 들어 빌드에는 Debian 계열 이미지를 쓰고 실행에는 Alpine을 쓰면 libc 차이로 모듈이 실행되지 않을 수 있습니다. 처음 최적화할 때는 동일 계열의 slim 이미지를 사용해 안정성을 확보한 뒤 필요하면 별도 검증을 거치는 편이 안전합니다.
.dockerignore로 빌드 컨텍스트부터 줄이기
이미지 레이어만 줄여도 빌드 컨텍스트가 크면 Docker 데몬으로 전송하는 시간이 계속 발생합니다. .git, 로컬 node_modules, 테스트 결과, 비밀 설정 파일은 대개 컨테이너 빌드에 필요하지 않습니다. 특히 .env 파일이 빌드 컨텍스트에 포함되면 COPY 명령을 쓰지 않았더라도 관리 실수로 이미지에 들어갈 위험이 생깁니다.
node_modules
.git
.env
.env.*
coverage
dist
*.log
Dockerfile*
docker-compose*.yml
README.md단, dist를 무조건 제외하면 빌드 방식에 따라 문제가 될 수 있습니다. 위 Dockerfile처럼 컨테이너 내부에서 npm run build를 실행하는 경우에는 제외해도 됩니다. 반대로 CI에서 이미 만든 dist를 복사하는 방식이라면 dist를 제외하지 않아야 합니다. .dockerignore는 프로젝트의 실제 빌드 책임 위치에 맞춰 관리해야 합니다.
비밀값은 이미지와 Dockerfile에 넣지 않는다
ENV, ARG, COPY로 전달한 값은 이미지 레이어나 빌드 이력에 남을 수 있습니다. API 키, 데이터베이스 비밀번호, 개인 인증서는 Dockerfile에 작성하지 말고 배포 환경의 시크릿 저장소나 컨테이너 실행 환경에서 주입합니다. 빌드 중 비공개 패키지 저장소 인증이 필요하다면 BuildKit secret 기능을 사용하고, 결과물에 인증 파일이 남지 않는지 확인해야 합니다.
docker build --secret id=npmrc,src=$HOME/.npmrc -t my-service:2026-07-23 .
docker run --rm -p 3000:3000 \
--env-file ./runtime.env \
my-service:2026-07-23운영에서는 runtime.env 파일 자체도 소스 저장소에 넣지 않습니다. Kubernetes, CI/CD, 클라우드 시크릿 관리 서비스처럼 접근 제어와 변경 이력이 있는 전달 경로를 선택하는 것이 좋습니다. 컨테이너를 root로 실행하지 않는 것도 중요합니다. 애플리케이션이 침해되더라도 호스트와 볼륨에 미치는 범위를 줄일 수 있습니다.
캐시와 재현 가능한 빌드를 관리하는 방법
Docker는 명령과 입력 파일이 같으면 이전 레이어를 재사용합니다. package.json과 lock 파일을 먼저 복사한 뒤 의존성을 설치하면, 애플리케이션 소스만 수정한 배포에서 의존성 설치 레이어를 다시 만들지 않아도 됩니다. 반대로 COPY . .을 먼저 실행하면 작은 소스 수정도 의존성 설치 캐시를 무효화합니다. lock 파일을 사용하는 npm ci는 설치 결과를 고정하므로 서버와 개발 PC의 패키지 차이를 줄이는 데도 유리합니다.
베이스 이미지는 latest 태그만 사용하지 말고 검증한 메이저 또는 구체 태그로 관리합니다. 정기적으로 베이스 이미지와 의존성 취약점 점검을 실행하고, 이미지 크기 변화도 배포 파이프라인에서 기록하면 불필요한 파일 유입을 빨리 발견할 수 있습니다. 이미지 크기만 작은 것이 목표가 아니라, 재현 가능하고 점검 가능한 배포 단위를 만드는 것이 핵심입니다.
배포 전 체크리스트
- 빌드 단계와 실행 단계를 분리하고 최종 이미지에 컴파일 도구가 남지 않는지 확인한다.
- .dockerignore에 로컬 의존성, Git 이력, 비밀 설정 파일, 테스트 산출물을 포함한다.
- lock 파일 기반 설치를 사용하고 의존성 파일을 먼저 복사해 캐시를 활용한다.
- 비밀값은 이미지가 아니라 실행 환경 또는 시크릿 관리 도구에서 주입한다.
- root가 아닌 전용 사용자로 실행하고, 새 이미지에서 실제 기동과 헬스 체크를 검증한다.
tool
| No | 작성일 | Title |
|---|---|---|
| 1904 | 2023. 05. 17. | intellij 에서 프로젝트 복사하기 (git 포함) |
| 1685 | 2020. 12. 12. | [ tomcat ] rule 설정하기, www 붙이기, www 지우기, 301,302 |
| 1402 | 2018. 11. 20. | [ 이클립스 ] 이미 만들어논 프로젝트를 git에 생성하기 |
| 1361 | 2017. 11. 19. | [ 이클립스 ] 이클립스에서 git 사용시 merge 또는 커밋 취소하기 |
| 347 | 2016. 02. 11. | [ 이클립스 ] eclipse 메이븐(maven) 설정시 pom.xml 에러 |
| 343 | 2016. 02. 06. | [ eclipse ] 이클립스 maven - update project 적용시 자바 버전 변경될경우(고정방법) |
| 331 | 2015. 11. 13. | [ eclipse ] 이클립스 자바스클립트( javascript ) 소스 복사 느릴때 |
| 318 | 2015. 10. 16. | [ maven ] maven update 시 java compile 버전이 변경될때 |
| 319 | 2015. 10. 16. | [ eclipse ] 이클립스에서 aspectJ 가 ruler 에 표시되지 않을때 |
| 309 | 2015. 06. 26. | [ eclipse ] 이클립스 오프라인 환경에서 xml 자동완성 설정하기 |