Dockerfile에 FROM이 두 번 나온다고 해서 최종 이미지가 자동으로 작아지는 것은 아니다. 최종 단계에 무엇을 복사하는지가 핵심이다. 이 글은 이 저장소의 Dockerfile을 2026년 7월 23일 기준으로 점검한 결과와 측정 방법을 정리한다.

현재 Dockerfile에서 확인한 사실

현재 파일의 주요 명령은 다음과 같다.

FROM node:22-slim AS builder
...
RUN npm ci; npx quartz plugin install

FROM node:22-slim
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/ /usr/src/app/
COPY . .
CMD ["npx", "quartz", "build", "--serve"]

읽기 전용 검사 결과는 다음과 같다.

검사 항목현재 값의미
FROM 수2builder와 최종 단계는 구분되어 있다.
builder 베이스node:22-slimGit 설치, npm ci, Quartz 플러그인 설치를 수행한다.
최종 베이스node:22-slimbuilder보다 더 작은 런타임 베이스는 아니다.
단계 간 복사/usr/src/app 전체빌드 전용 의존성도 함께 복사될 수 있다.
소스 복사COPY . .저장소 전체가 다시 들어간다.
.dockerignore없음.git, public, 로컬 도구가 빌드 컨텍스트에 포함될 수 있다.

따라서 이 Dockerfile은 멀티스테이지 문법을 사용하지만 “최종 산출물만 옮기는 슬림 이미지”라고 단정할 수 없다. 이미지 크기가 몇 퍼센트 줄었다는 기존 수치도 이 저장소에서 측정한 적이 없어 삭제했다.

멀티스테이지의 판단 기준

Docker의 공식 멀티스테이지 문서는 각 FROM이 새 단계를 시작하고 COPY —from으로 필요한 산출물만 선택할 수 있다고 설명한다.

질문좋은 신호점검이 필요한 신호
최종 단계에 무엇을 복사하는가?public, dist, 단일 바이너리builder 작업 디렉터리 전체
실행에 컴파일러가 필요한가?필요 없음Git, gcc, 테스트 도구가 남음
베이스가 다른가?build와 runtime 역할이 구분됨두 단계가 같고 전체 파일을 복사
빌드 컨텍스트가 제한되는가?.dockerignore가 있음저장소 전체를 전송
실행 사용자가 제한되는가?비루트 USER기본 root

멀티스테이지는 보안 기능 그 자체도 아니다. 패키지가 적어지면 공격 표면을 줄이는 데 도움이 될 수 있지만, 베이스 이미지 업데이트, 비루트 사용자, 비밀 관리, 취약점 스캔은 별도 작업이다.

정적 사이트에 맞춘 목표 구조

Quartz를 빌드한 뒤 정적 파일만 제공하려는 목적이라면 개념적으로 다음처럼 역할을 분리할 수 있다. 이 코드는 현재 Dockerfile에 적용된 결과가 아니라 검토용 예시다.

FROM node:22-slim AS builder
WORKDIR /app
COPY package.json package-lock.json quartz.lock.json ./
COPY quartz/ ./quartz/
RUN npm ci && npx quartz plugin install
COPY content/ ./content/
COPY quartz.config.yaml quartz.ts ./
RUN node ./quartz/bootstrap-cli.mjs build

FROM nginx:alpine AS runtime
COPY --from=builder /app/public/ /usr/share/nginx/html/

실제 적용 전에는 Quartz가 빌드 시 읽는 모든 설정과 정적 자산을 목록화해야 한다. 이 저장소는 플러그인과 커스텀 컴포넌트를 사용하므로 위의 COPY 목록만으로 빌드가 된다고 보장할 수 없다.

서버 미리보기까지 컨테이너 안에서 실행해야 한다면 정적 웹 서버 대신 Node 런타임을 남기는 별도 목표가 필요하다.

.dockerignore 후보

현재 저장소에는 .dockerignore가 없다. 다음 항목은 검토할 수 있지만, 무조건 복사해서 쓰면 안 된다.

.git
.github
node_modules
public
.tools
*.log
python-server.*.log

예를 들어 빌드 과정에서 Git 메타데이터로 수정일을 계산한다면 .git 제외가 결과를 바꿀 수 있다. 제외 목록은 실제 빌드가 통과하는지 확인하면서 추가해야 한다.

이미지 크기와 내용을 재현해서 비교하기

Docker가 설치된 환경에서는 추측 대신 두 이미지를 같은 조건으로 빌드한다.

docker build --no-cache -t confusion-blog:current -f Dockerfile .
docker image inspect confusion-blog:current --format "{{.Size}}"
docker history --no-trunc confusion-blog:current

개선 Dockerfile을 별도 파일에 만들었다면 다음처럼 비교한다.

docker build --no-cache -t confusion-blog:candidate -f Dockerfile.candidate .
docker image inspect confusion-blog:candidate --format "{{.Size}}"
docker history --no-trunc confusion-blog:candidate

비교표에는 최소한 다음 조건을 함께 기록해야 한다.

필드기록 예
커밋git rev-parse —short HEAD
플랫폼linux/amd64 또는 linux/arm64
캐시—no-cache 사용 여부
베이스 digestdocker image inspect 결과
최종 바이트두 이미지의 Size
빌드 시간같은 호스트에서 여러 번 측정한 중앙값

Docker의 빌드 권장사항도 멀티스테이지와 재사용 가능한 단계를 설명한다.

한계

  • 이 작업 환경에는 docker 명령이 설치되어 있지 않아 이미지 빌드와 크기 비교를 실행하지 못했다.
  • 따라서 이 글은 MB, 백분율, 배포 시간 절감 수치를 제시하지 않는다.
  • 예시 nginx 런타임은 Quartz의 개발 서버 기능과 동적 동작을 제공하지 않는다.
  • Alpine과 slim의 크기 차이가 호환성 문제보다 항상 중요한 것은 아니다. 네이티브 모듈과 libc 차이를 테스트해야 한다.
  • 이미지가 작아도 취약점이 0개라는 뜻은 아니다.
  • 현재 Dockerfile을 수정한 글이 아니므로 실제 변경 전 CI와 컨테이너 실행 테스트가 필요하다.

정확한 결론은 “멀티스테이지를 썼다”가 아니라 동일한 커밋을 빌드한 이미지의 바이트와 실행 결과로 내려야 한다.

📦추천 상품🔒 개인정보 수집 없음

쿠팡 로켓배송 특가 & 가성비 추천 제품 확인

이 글에서 소개하거나 추천하는 IT 기기, 가공식품, 가성비 필수 꿀템을 쿠팡 로켓배송 최저가로 빠르게 만나보세요.

쿠팡 최저가 확인하기 →