Contents
see List배포 이미지는 왜 작고 단순해야 하는가
Docker 이미지는 애플리케이션 실행에 필요한 파일과 환경을 한 묶음으로 전달한다. 하지만 개발 단계의 컴파일러, 패키지 캐시, 테스트 도구, 소스 맵, 인증 정보까지 운영 이미지에 남으면 배포 속도와 보안 검토 비용이 함께 커진다. 이미지가 커질수록 CI에서 레지스트리로 올리고 서버에서 내려받는 시간이 길어지고, 취약점 스캐너가 확인해야 할 패키지도 많아진다. 운영 컨테이너에는 애플리케이션 실행 결과물과 필요한 런타임만 남기는 것이 기본 원칙이다.
가장 실용적인 방법은 멀티 스테이지 빌드다. 앞 단계에서는 의존성을 설치하고 컴파일하며, 마지막 단계에서는 실행에 필요한 파일만 복사한다. 작은 베이스 이미지를 선택하는 것도 중요하지만, 먼저 불필요한 파일이 최종 레이어에 들어가지 않도록 구조를 분리해야 한다.
Node.js 서비스용 멀티 스테이지 Dockerfile
아래 예시는 TypeScript 기반 Node.js API를 대상으로 한다. 의존성 설치 단계와 빌드 단계를 분리하고, 최종 이미지에는 production 의존성과 dist 결과만 넣는다. lock 파일을 먼저 복사하면 소스 코드가 바뀌어도 의존성 설치 레이어를 재사용할 수 있어 빌드 시간이 안정된다.
FROM node:22-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM deps AS build
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
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]핵심은 runtime 단계가 build 단계 전체를 복사하지 않는다는 점이다. TypeScript, eslint, test runner 같은 개발 의존성은 운영 컨테이너에 포함되지 않는다. 다만 네이티브 모듈을 쓰는 서비스는 빌드 단계와 실행 단계의 OS 계열 및 라이브러리 호환성을 먼저 확인해야 한다. 문제가 발생하면 동일한 배포 계열 이미지를 유지하거나 해당 모듈의 사전 빌드 지원 여부를 점검한다.
.dockerignore로 빌드 컨텍스트부터 줄이기
Docker는 빌드할 때 현재 디렉터리의 파일을 빌드 컨텍스트로 전송한다. Dockerfile에서 COPY하지 않더라도 컨텍스트에 포함된 대용량 파일은 빌드를 느리게 할 수 있다. 특히 node_modules, Git 이력, 로컬 환경 파일, 테스트 산출물은 제외 대상이다. .dockerignore는 이미지 크기뿐 아니라 실수로 민감한 파일을 COPY하는 위험도 낮춘다.
node_modules
dist
.git
.env
.env.*
coverage
npm-debug.log
Dockerfile*
docker-compose*.yml단, 운영 환경에서 필요한 예시 설정 파일까지 무심코 제외하지 않도록 주의한다. 실제 비밀값은 이미지에 넣지 않고 배포 플랫폼의 secret 또는 환경 변수 주입 기능으로 전달한다. 이미지 레이어에 한 번 기록된 토큰은 이후 레이어에서 삭제해도 이전 레이어 기록에 남을 수 있으므로, ARG나 COPY로 비밀값을 전달하는 방식도 피해야 한다.
캐시를 빠르게 만들되 결과는 재현 가능하게 유지하기
Dockerfile의 명령 순서는 캐시 효율에 직접 영향을 준다. 자주 바뀌지 않는 package-lock.json을 먼저 복사하고 npm ci를 실행한 뒤, 자주 바뀌는 소스 코드를 복사한다. npm install 대신 npm ci를 사용하면 lock 파일 기준으로 정확한 의존성 트리를 설치하므로 CI와 운영 빌드 결과 차이를 줄일 수 있다. 베이스 이미지도 latest 태그에 의존하기보다 팀의 업데이트 정책에 맞는 명시적 메이저 버전 또는 검증한 digest를 사용한다.
BuildKit을 사용하는 환경에서는 패키지 다운로드 캐시를 빌드 전용으로 활용할 수 있다. 이 캐시는 최종 이미지에 포함되지 않으면서 반복 빌드 시간을 줄인다. CI에서 Docker Buildx를 사용한다면 레지스트리 캐시나 CI 제공 캐시를 연결해 같은 효과를 확장할 수 있다.
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm npm ci
docker buildx build \
--tag registry.example.com/team/api:20260817 \
--push .운영 전 확인할 보안 항목
- 최종 이미지에 빌드 도구, 테스트 파일, .env, 개인 키가 없는지 확인한다.
- root 대신 전용 사용자로 실행하고, 애플리케이션이 필요한 포트와 파일 권한만 부여한다.
- 베이스 이미지와 npm 의존성의 취약점 검사를 CI 단계에 추가하고 긴급 업데이트 기준을 정한다.
- 태그만으로 배포 대상을 판단하지 말고 Git 커밋 SHA 또는 빌드 번호를 함께 기록한다.
- 컨테이너 시작 명령이 개발용 watch 모드가 아닌 운영용 서버 프로세스인지 점검한다.
기술 요약 체크리스트
운영 이미지는 실행 결과물과 최소 런타임만 포함하고, 빌드와 테스트 도구는 멀티 스테이지의 앞 단계에 둔다. .dockerignore로 전송 파일을 제한하고, lock 파일 기반 설치와 레이어 순서로 빌드 캐시를 안정화한다. 마지막으로 비밀값 미포함, non-root 실행, 취약점 점검, 추적 가능한 이미지 태그를 배포 파이프라인의 필수 검증 항목으로 관리하면 이미지 용량 절감이 실제 운영 안정성으로 이어진다.
tool
| No | 작성일 | Title |
|---|---|---|
| 2144 | 2026. 02. 11. | GitHub Actions 고급 워크플로우 설계 패턴 |
| 2143 | 2026. 02. 11. | Docker Compose v2와 컨테이너 오케스트레이션 실전 |
| 2142 | 2026. 02. 11. | Claude Code 완전 가이드: AI 코딩 에이전트 활용법 |
| 2033 | 2025. 11. 30. | Grafana + Prometheus 모니터링 구축 |
| 2032 | 2025. 11. 30. | Terraform으로 인프라 코드화 (IaC) |
| 2031 | 2025. 11. 30. | Vim 에디터 기초와 생산성 향상 |
| 2030 | 2025. 11. 30. | Postman으로 API 테스트 자동화 |
| 2029 | 2025. 11. 30. | Jenkins CI/CD 파이프라인 구축 |
| 2028 | 2025. 11. 30. | Maven vs Gradle - 빌드 도구 비교 |
| 2027 | 2025. 11. 30. | IntelliJ IDEA 생산성 향상 단축키 |