Dockerfile은 이미지를 만드는 명령을 순서대로 적은 파일이다. 파일시스템을 바꾸는 명령(RUN·COPY·ADD)이 각각 레이어(layer)가 되고, 바뀌지 않은 레이어는 캐시를 재사용한다. 그래서 자주 바뀌는 것을 뒤에 두는 순서가 빌드 속도를 좌우한다.
레이어 캐시 살리기
어떤 레이어가 바뀌면 그 뒤의 레이어는 전부 다시 만든다. 소스를 통째로 복사한 뒤 의존성을 설치하면, 코드 한 줄만 고쳐도 설치를 매번 다시 한다.
FROM node:22-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./ # 의존성 목록만 먼저
RUN corepack enable && pnpm install --frozen-lockfile
COPY . . # 소스는 나중에
RUN pnpm build
CMD ["node", "dist/index.js"].dockerignore에 node_modules, .git, 빌드 산출물을 넣어 복사 대상과 빌드 컨텍스트(build context)를 줄인다. 비밀 파일(.env)도 여기서 막는다.
멀티 스테이지 빌드
빌드 도구는 빌드 때만 필요하다. 단계를 나눠 마지막 이미지에는 실행에 필요한 결과물만 담는다.
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html- 최종 이미지가 작아져 배포가 빠르고, 공격 표면(attack surface: 셸·컴파일러)도 줄어든다
- SPA를 nginx로 서빙하는 구성은 nginx로 SPA 배포를 본다
그 밖에
docker image history <이미지>로 레이어별 크기를 보고 비대한 단계를 찾는다- 베이스 이미지 태그를 고정하고(
node:22-alpine), 주기적으로 취약점 스캔을 돌린다 - 비밀값은
ENV나COPY로 이미지에 굽지 않는다. 레이어에 영원히 남는다
캐시가 입력 해시로 결과를 재사용한다는 점은 Turborepo의 태스크 캐시와 같은 원리이고, 결국 번들 다이어트처럼 "필요 없는 것을 싣지 않는" 문제다.