← 아티클 목록

Dockerfile 작성 베스트 프랙티스 정리

2028-05-15#docker#dockerfile#최적화

Dockerfile은 일단 동작하게 만들기는 쉽습니다. 문제는 그렇게 쓴 Dockerfile이 빌드는 매번 느리고, 이미지는 1GB를 넘고, 보안 점검에서 지적당한다는 것입니다. 베스트 프랙티스라고 부르는 것들은 사실 세 가지 목표로 수렴합니다 — 빌드 속도, 이미지 크기, 보안. 이 세 축을 기준으로 핵심만 정리합니다.

1. 레이어 캐시를 살리는 순서

Dockerfile 명령 하나가 대체로 레이어 하나를 만들고, 한 레이어가 바뀌면 그 아래 모든 레이어 캐시가 깨집니다. 그래서 자주 안 바뀌는 것을 위에, 자주 바뀌는 소스를 아래에 둬야 합니다.

Dockerfile
# 나쁨: 소스가 한 줄만 바뀌어도 npm install 재실행
COPY . .
RUN npm ci

# 좋음: 의존성 목록 먼저, 소스는 나중에
COPY package*.json ./
RUN npm ci
COPY . .

소스만 고쳤을 때 npm ci 캐시가 그대로 재사용돼 빌드가 몇 배 빨라집니다.

2. 멀티스테이지 빌드로 크기 줄이기

빌드에 필요한 컴파일러·개발 의존성이 최종 이미지에 그대로 남으면 용량이 폭증합니다. 멀티스테이지 빌드는 빌드용 스테이지와 실행용 스테이지를 나눠, 결과물만 가벼운 런타임 이미지로 복사합니다.

Dockerfile
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]

빌드 도구는 첫 스테이지에 버려지고, 최종 이미지에는 실행에 필요한 것만 남습니다.

3. 핵심 원칙 빠른 표

원칙이유
.dockerignore 작성node_modules·.git 제외로 빌드 컨텍스트 축소
태그 고정(node:20-slim)latest는 재현 불가·예측 불가
RUN 묶기 + 캐시 정리레이어 수↓, apt 캐시 같은 레이어에서 삭제
non-root 사용자컨테이너 탈취 시 권한 상승 차단
슬림/distroless 베이스공격 표면·용량 축소

.dockerignore는 특히 효과가 큰데, 거대한 node_modules나 로컬 빌드 산출물을 데몬에 보내지 않아 빌드 자체가 빨라집니다.

4. 보안 — non-root로 실행

컨테이너는 기본적으로 root로 돕니다. 앱이 뚫리면 컨테이너 안 root 권한을 그대로 내주는 셈이라, 전용 사용자를 만들어 권한을 낮춥니다.

Dockerfile
RUN useradd -m appuser
USER appuser

이렇게 하면 프로세스가 일반 사용자 권한으로 돌아 사고 시 피해 범위가 줄어듭니다.

언제 어디까지 적용하나

로컬에서 잠깐 돌리는 실험용이면 캐시 순서 정도만 챙겨도 됩니다. 하지만 CI에서 매번 빌드하거나 운영에 배포하는 이미지라면 멀티스테이지·non-root·.dockerignore까지 전부 적용하는 게 좋습니다. 규칙이 < 5개라 부담스러워 보여도, 한 번 템플릿을 잡아 두면 이후 모든 프로젝트에 그대로 재사용됩니다.

요점 정리

  • 변경 빈도 낮은 명령을 위에 둬 레이어 캐시를 살린다(의존성 먼저, 소스 나중).
  • 멀티스테이지 빌드로 빌드 도구를 버리고 런타임 이미지를 가볍게 한다.
  • .dockerignore·태그 고정·RUN 묶기로 크기와 재현성을 잡는다.
  • non-root 사용자와 슬림 베이스로 공격 표면을 줄인다.

이 원칙들을 직접 빌드하며 이미지 크기와 빌드 시간이 어떻게 변하는지 확인하는 실습은 도커 트랙에서 할 수 있습니다 — 회원가입 없이 무료로.