같은 Dockerfile 인데 왜 어떤 빌드는 3초, 어떤 빌드는 3분인가
앞 장 끝에서 예고했던 질문입니다. 왜 package.json 을 먼저 복사하고 npm install 을 실행한 다음에 나머지 코드를 복사할까요. 코드만 보면 순서를 바꿔도 결과는 똑같아 보입니다. 하지만 빌드할 때마다 걸리는 시간은 완전히 달라집니다.
Docker는 레이어 단위로 캐시를 확인합니다
Dockerfile의 각 명령은 레이어를 하나씩 만든다고 앞 장에서 설명했습니다. Docker는 빌드할 때 각 레이어에 대해 "이 레이어를 전에 만든 적이 있는가"를 확인합니다. 판단 기준은 이렇습니다.
COPY/ADD는 복사하는 파일의 내용이 이전과 같은지를 봅니다.RUN은 명령어 텍스트 자체가 이전과 같은지를 봅니다(실행 결과가 아니라 명령어 문자열입니다).- 그리고 결정적으로, 한 레이어가 캐시 미스가 나면 그 뒤의 모든 레이어도 자동으로 다시 빌드됩니다. 위에서부터 순서대로 확인하다가 하나라도 달라지면, 그 아래는 이전 캐시를 재사용할 이유가 없어지기 때문입니다.
flowchart TD A["FROM node:20-alpine"] --> B["COPY package.json"] B --> C["RUN npm install"] C --> D["COPY . ."] D --> E["CMD 설정"] style A fill:#ddd style B fill:#ddd style C fill:#f88 style D fill:#f88 style E fill:#f88
이 그림에서 package.json 이 그대로라면 A, B는 캐시를 그대로 쓰고, npm install 부터 다시 실행됩니다(빨간 부분). 코드가 바뀌었을 뿐 의존성은 그대로인 상황이라면, 이 순서 덕분에 npm install(보통 가장 오래 걸리는 단계)을 매번 건너뛸 수 있습니다.
순서를 반대로 하면 벌어지는 일
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]여기서는 COPY . . 가 소스 코드 전체를 복사합니다. 코드를 한 줄만 고쳐도 이 레이어는 캐시 미스가 나고, 그 아래의 RUN npm install 도 자동으로 다시 실행됩니다. 의존성은 하나도 안 바꿨는데도 매번 npm install 전체를 다시 돌리게 되는 겁니다. 의존성이 수백 개인 프로젝트라면 이 차이가 빌드 시간 3초와 3분을 가릅니다.
이건 npm/yarn/pnpm뿐 아니라 다른 언어에도 그대로 적용됩니다.
# Python 예시
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python3", "main.py"]원리는 같습니다. 자주 바뀌는 것(소스 코드)은 아래에, 잘 안 바뀌는 것(의존성 목록)은 위에 둡니다.
.dockerignore — COPY의 범위를 줄이는 또 다른 축
COPY . . 는 현재 디렉터리의 모든 파일을 복사합니다. node_modules, .git, 로컬 .env 파일까지 전부 이미지 빌드 컨텍스트로 딸려 들어갑니다. 이건 캐싱 효율에도 안 좋고(불필요한 파일이 바뀔 때마다 캐시가 깨짐), 이미지에 민감한 정보나 불필요한 용량이 섞여 들어갈 위험도 있습니다.
# .dockerignore
node_modules
.git
.env
*.log
dist.gitignore 와 비슷하게 생겼지만 별개의 파일입니다. 둘의 목적이 겹치는 경우가 많아서 내용도 비슷해지는 경우가 많지만, .dockerignore 가 없으면 .gitignore 에 뭐가 있든 Docker는 신경 쓰지 않고 전부 복사한다는 점을 기억해 두세요.
빌드 캐시를 강제로 무시하고 싶을 때
가끔 "분명히 코드도 안 바꿨는데 이상하게 캐시된 옛날 버전이 계속 실행된다"는 상황을 만납니다. 원인은 다양하지만(베이스 이미지의 latest 태그가 레지스트리에서는 바뀌었는데 로컬 캐시는 안 바뀐 경우 등), 일단 의심되면 캐시를 강제로 건너뛰고 처음부터 다시 빌드해서 확인하는 게 빠릅니다.
docker build --no-cache -t my-app .캐시를 안 쓰니 당연히 느립니다. 평소 빌드에 쓸 옵션이 아니라, "캐시가 문제인지 아닌지"를 확인하는 진단용 도구로 기억해 두면 됩니다.