Skip to content
SON BLOG
Go back

Dockerfile 최적화: COPY --chown vs chown -R 레이어 중복 제거

Edit page

배경

xgen-frontend는 Next.js 앱이다. 빌드 후 실행 이미지에서 파일 소유권을 nextjs:nodejs 사용자로 변경하는 작업이 필요했다. 처음 작성된 Dockerfile은 흔히 볼 수 있는 패턴이었다.

# 수정 전 Dockerfile (문제 있는 버전)
FROM node:20-alpine AS runner
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

WORKDIR /app

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

# 문제: COPY 후 별도 레이어로 소유권 변경
RUN chown -R nextjs:nodejs /app

USER nextjs
CMD ["node", "server.js"]

이 Dockerfile의 문제는 chown -R nextjs:nodejs /app이 별도 레이어를 만든다는 것이다. /app 디렉토리 전체의 메타데이터(소유권)를 변경하는 레이어가 추가되어, 실제 파일 데이터를 담은 레이어와 소유권 변경 레이어 두 개가 생긴다.

레이어 중복의 문제

Docker 이미지 레이어는 union filesystem으로 쌓인다. chown -R은 파일 내용을 바꾸지 않지만, 파일시스템 관점에서는 모든 파일의 메타데이터가 새 레이어에 기록된다.

# docker history로 레이어 확인 (수정 전)
docker history registry.x2bee.com/xgen-frontend:before

IMAGE         CREATED BY                                SIZE
sha256:abc    RUN chown -R nextjs:nodejs /app           45.2MB 불필요한 레이어
sha256:def    COPY --from=builder /app/.next/static     12.3MB
sha256:ghi    COPY --from=builder /app/.next/standalone 32.8MB

chown -R 레이어가 45.2MB나 됐다. standalone 빌드의 파일 크기와 거의 동일하다 — 파일 내용 없이 소유권 메타데이터만으로 이 크기가 나온다.

COPY —chown 적용

# # 커밋: Dockerfile COPY --chown으로 chown -R 레이어 제거 (이미지 크기 최적화)
# # 날짜: 2024-09-08
FROM node:20-alpine AS runner
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

WORKDIR /app

# COPY 시점에 소유권 지정 → chown 레이어 불필요
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

USER nextjs
CMD ["node", "server.js"]

COPY --chown=nextjs:nodejs는 파일을 복사하는 동시에 소유권을 설정한다. 별도 레이어 없이 COPY 레이어 하나로 끝난다.

# docker history (수정 후)
docker history registry.x2bee.com/xgen-frontend:after

IMAGE         CREATED BY                                           SIZE
sha256:xyz    COPY --chown=nextjs:nodejs /app/public ./public      2.1MB
sha256:uvw    COPY --chown=nextjs:nodejs .next/static .next/static 12.3MB
sha256:rst    COPY --chown=nextjs:nodejs .next/standalone ./       32.8MB

45.2MB짜리 chown -R 레이어가 사라졌다.

왜 이미지 크기가 줄어드나

Union filesystem의 레이어 특성 때문이다. chown -R을 실행하면 변경된 메타데이터를 담은 새 레이어가 추가된다. 이전 레이어의 파일은 이미 있고, 새 레이어에 “소유권이 바뀐 동일 파일”이 중복으로 기록된다.

레이어 N (COPY):
  /app/server.js  (root:root)
  /app/.next/...  (root:root)

레이어 N+1 (chown -R):
  /app/server.js  (nextjs:nodejs)  ← 내용은 같지만 메타데이터 변경
  /app/.next/...  (nextjs:nodejs)  ← 레이어에 재기록

COPY —chown을 쓰면 레이어 N에 처음부터 올바른 소유권으로 기록되므로 중복이 없다.

multi-stage build에서 소유권 유의사항

FROM node:20-alpine AS builder
# builder 단계에서는 root로 실행
RUN npm ci && npm run build

FROM node:20-alpine AS runner
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# builder의 root 소유 파일을 runner에서 nextjs 소유로 복사
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./

multi-stage build에서 COPY --from=builder를 쓸 때도 --chown을 지정할 수 있다. builder 이미지에서 root 소유였던 파일이 runner 이미지에서 nextjs 소유로 복사된다.

언제 chown -R이 필요한가

COPY --chown이 모든 상황을 커버하지는 않는다.

chown -R이 여전히 필요한 경우:

# 런타임 디렉토리는 여전히 RUN으로 처리
RUN mkdir -p /app/logs /app/tmp && \
    chown -R nextjs:nodejs /app/logs /app/tmp

이런 경우는 어쩔 수 없지만, 디렉토리만 생성하는 것이라 크기가 작다.

실제 효과

구분수정 전수정 후
이미지 레이어 수12개11개
chown 레이어 크기45.2MB0MB
최종 이미지 크기182MB137MB
이미지 푸시 시간2분 10초1분 35초

이미지가 45MB 줄었고, 레지스트리 푸시 속도도 개선됐다. 레이어 재사용률도 올라가서 동일 코드 변경 시 변경된 레이어만 푸시된다.

Dockerfile 레이어 최적화 원칙 정리

실제 적용하면서 정리한 레이어 최적화 원칙들이다.

# 나쁜 예: 불필요한 레이어 추가
COPY . .
RUN chown -R app:app .
RUN chmod +x entrypoint.sh

# 좋은 예: COPY 시점에 권한 설정
COPY --chown=app:app . .
# 나쁜 예: RUN 명령 분리 (레이어 각각 생성)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# 좋은 예: && 로 체이닝 (레이어 1개)
RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*
# 나쁜 예: 변경 빈도 높은 파일을 먼저 COPY
COPY . .
RUN npm ci

# 좋은 예: 변경 빈도 낮은 파일을 먼저 COPY (캐시 히트율 향상)
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

결과

Docker 이미지 최적화는 빌드 시간과 배포 속도 모두에 영향을 준다. COPY --chown은 적용이 단순하면서 효과가 큰 최적화다.


Edit page
Share this post:

Previous Post
Docker BuildKit 캐시 전략과 NO_CACHE 옵션
Next Post
Jenkins RBAC: Kubernetes watch 권한 누락으로 인한 배포 실패 삽질기