infra
Platform

모듈 맵

[Docker] 대규모 빌드 속도를 10배 끌어올리는 캐시 튜닝 가이드

0 / 27 완료

펼치기
0 / 27 완료0%

Docker · 23 / 27

[Docker] 대규모 빌드 속도를 10배 끌어올리는 캐시 튜닝 가이드

BuildKit의 RUN --mount=type=cache, 병렬 빌드, 시크릿 마운트로 빌드 속도를 극적으로 개선합니다

BuildKit 고급 빌드 캐시와 빌드 최적화

🚨INCIDENT ALERT
HIGH

PR마다 빌드가 10분 이상 걸려 리뷰 피드백이 늦어지고, 팀 전체 배포 리듬이 느려집니다. 원인을 보면 의존성 다운로드를 매번 처음부터 반복하고, CI는 캐시를 거의 재사용하지 못합니다. BuildKit은 단순 속도 개선이 아니라 빌드 파이프라인의 병목을 구조적으로 제거하는 도구입니다. 이 모듈은 캐시 마운트·시크릿 마운트·레지스트리 캐시 전략을 실전 형태로 정리합니다.

Docker 17.05에서 멀티스테이지 빌드가 도입되었다면, BuildKit은 빌드 엔진 자체를 재설계한 변화입니다. 기존 레이어 캐시의 한계를 극복하는 마운트 타입 캐시, 빌드 시크릿 안전 주입, DAG 기반 병렬 실행까지 — BuildKit을 제대로 활용하면 10분짜리 빌드를 2분 안에 끝낼 수 있습니다.


이번 챕터에서 배울 것

BuildKit의 고급 기능을 단계적으로 익혀 실제 프로젝트의 빌드 시간을 극적으로 단축하는 방법을 실습합니다. 특히 CI/CD 파이프라인에서 캐시를 활용하는 전략을 중점적으로 다룹니다.

  • 1BuildKit 활성화(DOCKER_BUILDKIT=1)와 docker buildx — 레거시 빌더와의 차이
  • 2RUN --mount=type=cache로 npm/pip/apt 패키지 캐시 빌드 간 재사용
  • 3RUN --mount=type=secret으로 빌드 시 비밀값 주입 — 이미지 레이어에 흔적 없음
  • 4DAG 기반 병렬 스테이지 빌드로 멀티스테이지 빌드 시간 단축
  • 5--cache-from과 레지스트리 캐시 패턴 — CI 환경 빌드 캐시 전략
  • 6docker build --progress=plain으로 빌드 단계별 시간 프로파일링
실습 환경 준비

Docker 23.0 이상 환경에서는 BuildKit이 기본 빌더로 사용됩니다. 구버전 환경에서는 DOCKER_BUILDKIT=1 환경 변수를 설정해야 합니다. docker buildx는 BuildKit 기반의 확장 빌드 CLI입니다.

BuildKit 지원 Docker 버전 확인 (23.0 이상 권장)
docker version --format '{{.Server.Version}}'
docker buildx 플러그인 설치 여부 확인
docker buildx version
기본 buildx 빌더 생성 (docker-container 드라이버)
docker buildx create --use --name mybuilder --driver docker-container
실습 디렉토리 준비
mkdir -p ~/buildkit-lab && cd ~/buildkit-lab
BuildKit 강제 활성화 환경 변수 (구버전 Docker 호환)

export DOCKER_BUILDKIT=1 — Docker 23.0 이상에서는 기본 활성화됩니다

💡개념

BuildKit이란 무엇인가 — 레거시 빌더와의 근본 차이

PR 하나 올릴 때마다 GitHub Actions 빌드가 15분 걸립니다. 팀원 5명이 오전에 동시에 PR을 올리면 빌드 큐가 쌓여 한 시간이 넘어갑니다. 개발자들이 피드백을 기다리다 다른 작업으로 넘어가고, 결국 컨텍스트 스위칭 비용이 생깁니다. 느린 빌드의 주범은 대부분 패키지 설치 — npm install, pip install이 매번 수백 MB를 새로 내려받기 때문입니다. BuildKit은 Docker 빌드 엔진 자체를 재설계한 것으로, 기존 레이어 캐시의 구조적 한계를 해결합니다.

BuildKit vs 레거시 빌더 비교

레거시 빌더(Classic Builder)의 한계

Docker 23.0 이전까지 기본으로 사용하던 레거시 빌더는 다음과 같은 구조적 한계를 가졌습니다.

레거시 빌더 동작 방식:
Dockerfile의 각 줄을 위에서 아래로 순서대로 실행
→ 의존관계 없는 스테이지도 직렬 실행
→ RUN 명령의 부산물(패키지 캐시 등)이 레이어에 포함
→ 빌드 시크릿을 안전하게 주입할 방법 없음
→ 캐시 무효화 시 해당 줄 이후 모든 레이어 재빌드

BuildKit의 핵심 개선점

BuildKit 동작 방식:
Dockerfile 전체를 파싱 → 의존 그래프(DAG) 생성
→ 독립 스테이지 병렬 실행 (CPU 코어 활용)
→ --mount=type=cache: 패키지 캐시를 레이어 외부에 유지
→ --mount=type=secret: 빌드 시크릿을 임시 마운트
→ --mount=type=ssh: SSH 에이전트 포워딩
→ 최종 이미지에 불필요한 레이어 포함 없음

BuildKit 활성화 방법

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/docker/part5/exam_23 && cd /tmp/docker/part5/exam_23

# 방법 1: 환경 변수로 활성화 (구버전 Docker)
export DOCKER_BUILDKIT=1
docker build -t myapp:latest .

# 방법 2: docker buildx 사용 (권장)
docker buildx build -t myapp:latest .

# 방법 3: /etc/docker/daemon.json 으로 전역 설정
# { "features": { "buildkit": true } }

# BuildKit 활성화 확인
docker buildx ls
# NAME/NODE       DRIVER/ENDPOINT  STATUS   BUILDKIT  PLATFORMS
# mybuilder *     docker-container running  v0.12.5   linux/amd64
# default         docker           running  v0.11.7   linux/amd64

Dockerfile 첫 줄 — syntax 지시어

BuildKit은 # syntax= 지시어로 Dockerfile 파서 버전을 지정할 수 있습니다.

Dockerfile
# syntax=docker/dockerfile:1.6
FROM node:20-alpine

# 이 지시어로 최신 BuildKit 기능을 안정적으로 사용할 수 있습니다
# 1.6 버전부터 --mount=type=cache의 sharing 옵션 개선

💡개념

RUN --mount=type=cache — 빌드 간 패키지 캐시 영구 재사용

의존성 라이브러리 하나를 추가했습니다. package.json에 한 줄이 바뀌었고, 덕분에 npm install 레이어 캐시가 통째로 무효화됩니다. 1,234개 패키지를 다시 내려받는 데 7분이 넘게 걸립니다. 정작 바뀐 것은 패키지 하나인데 말입니다. --mount=type=cache는 패키지 캐시를 이미지 레이어 바깥 별도 공간에 보관합니다. package.json이 바뀌어도 이미 내려받은 패키지는 그대로 남아 있어서 변경된 것만 추가로 받습니다.

cache mount 동작 원리

기존 레이어 캐시의 문제

기존 Dockerfile에서 패키지 설치 레이어 캐시는 package.json이 변경되는 순간 완전히 무효화됩니다. 의존성이 1개만 추가되어도 npm install 전체를 처음부터 다시 실행합니다.

Dockerfile
# 기존 방식 — package.json 변경 시 전체 재설치
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci                  # 캐시 무효화 시 수백 MB 재다운로드
COPY . .
RUN npm run build

--mount=type=cache 원리

--mount=type=cache는 BuildKit 호스트의 별도 캐시 스토리지에 디렉토리를 마운트합니다. 이 캐시는:

  • 이미지 레이어에 포함되지 않음 → 최종 이미지 크기 영향 없음
  • 빌드 간에 유지됨 → 패키지 다운로드를 건너뜀
  • 여러 빌드가 동시에 접근해도 안전sharing 옵션으로 제어
Dockerfile
# syntax=docker/dockerfile:1.6
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./

# npm 캐시 디렉토리를 BuildKit 캐시에 마운트
RUN --mount=type=cache,target=/root/.npm \
    npm ci

COPY . .
RUN npm run build

언어별 캐시 마운트 설정

Node.js (npm/yarn/pnpm)

Dockerfile
# npm — 캐시 디렉토리: /root/.npm
RUN --mount=type=cache,target=/root/.npm \
    npm ci --prefer-offline

# yarn — 캐시 디렉토리: /usr/local/share/.cache/yarn
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \
    yarn install --frozen-lockfile

# pnpm — 캐시 디렉토리: /root/.local/share/pnpm/store
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
    pnpm install --frozen-lockfile

Python (pip)

Dockerfile
# pip — 캐시 디렉토리: /root/.cache/pip
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --no-cache-dir -r requirements.txt
# 주의: --no-cache-dir은 pip의 내장 캐시를 끄는 것이 아님
# BuildKit 마운트 캐시와 별개 개념

apt (Debian/Ubuntu 계열)

Dockerfile
# apt — 캐시 디렉토리: /var/cache/apt, /var/lib/apt
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && apt-get install -y --no-install-recommends \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

Go (모듈 캐시)

Dockerfile
# Go — 모듈 캐시와 빌드 캐시 모두 마운트
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -o /app/server ./cmd/server

sharing 옵션

여러 빌드가 동시에 같은 캐시에 접근할 때의 동작을 제어합니다.

Dockerfile
# shared (기본값): 여러 빌드가 동시에 읽기/쓰기 가능
RUN --mount=type=cache,target=/root/.npm,sharing=shared \
    npm ci

# locked: 한 번에 하나의 빌드만 접근 (apt 같은 잠금 파일이 있는 경우)
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    apt-get update

# private: 각 빌드가 독립적인 캐시 사본 사용 (격리 필요 시)
RUN --mount=type=cache,target=/tmp/build,sharing=private \
    make build

💡개념

RUN --mount=type=secret — 빌드 시 비밀값 안전 주입

secret mount 안전 주입

왜 ARG로 시크릿을 전달하면 위험한가

Dockerfile
# ❌ 절대 하지 말 것 — ARG로 시크릿 전달
ARG NPM_TOKEN
RUN npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN

# docker history로 토큰 노출 확인 가능:
# docker history myimage --no-trunc
# IMAGE  CREATED  CREATED BY
# ...    ...      /bin/sh -c npm config set //registry.npmjs.org/:_authToken=npm_xxxxxxxxxxxx

docker history, docker inspect, 레이어 추출(docker save)로 ARG로 전달한 시크릿이 완전히 노출됩니다.

--mount=type=secret 동작 방식

빌드 호스트                    BuildKit 빌드 컨테이너
───────────────                ─────────────────────────────
~/.npmrc (토큰 포함)  ──────▶  /run/secrets/npmrc (임시 마운트)
                               RUN 명령 실행 중에만 존재
                               빌드 완료 후 파일 사라짐
                               최종 이미지 레이어에 포함 안 됨

사용법

private npm registry 인증

Dockerfile
# syntax=docker/dockerfile:1.6
FROM node:20-alpine

WORKDIR /app
COPY package*.json ./

# --mount=type=secret으로 .npmrc를 빌드 시에만 마운트
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    --mount=type=cache,target=/root/.npm \
    npm ci

COPY . .
RUN npm run build

빌드 명령:

로컬 터미널
# 방법 1: 파일로 시크릿 전달
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
docker buildx build \
  --secret id=npmrc,src=.npmrc \
  -t myapp:latest .

# 방법 2: 환경 변수에서 직접 전달
docker buildx build \
  --secret id=npmrc,env=NPM_TOKEN \
  -t myapp:latest .

pip private index 인증

Dockerfile
# syntax=docker/dockerfile:1.6
FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .

RUN --mount=type=secret,id=pip_conf,target=/root/.config/pip/pip.conf \
    --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt
Docker
# pip.conf 파일 예시
# [global]
# index-url = https://user:token@private.pypi.company.com/simple/

docker buildx build \
  --secret id=pip_conf,src=./pip.conf \
  -t myapp:latest .

SSH 에이전트 포워딩 (private Git 리포지토리 클론)

Dockerfile
# syntax=docker/dockerfile:1.6
FROM golang:1.21-alpine

# SSH known_hosts 설정
RUN mkdir -p /root/.ssh && \
    ssh-keyscan github.com >> /root/.ssh/known_hosts

WORKDIR /app

# SSH 에이전트를 마운트하여 private 리포지토리에서 모듈 다운로드
RUN --mount=type=ssh \
    --mount=type=cache,target=/go/pkg/mod \
    go mod download
로컬 터미널
# SSH 에이전트가 키를 로드하고 있는지 확인
ssh-add -l

docker buildx build \
  --ssh default \
  -t myapp:latest .

시크릿 노출 여부 검증

로컬 터미널
# 빌드 후 이미지 히스토리 확인 — 시크릿이 보이지 않아야 함
docker history myapp:latest --no-trunc | grep -i token
# (아무것도 출력되지 않아야 정상)

# 레이어 직접 검사
docker save myapp:latest | tar -tvf - | grep npmrc
# (아무것도 출력되지 않아야 정상)

💡개념

DAG 기반 병렬 빌드와 --cache-from 레지스트리 캐시

멀티스테이지 Dockerfile에서 프론트엔드 빌드(45초)와 백엔드 빌드(40초)가 순서대로 실행됩니다. 둘은 서로 아무런 관련이 없는데도 백엔드가 끝날 때까지 프론트엔드가 기다립니다. 총 시간이 95초입니다. 두 스테이지가 동시에 돌았다면 55초면 충분합니다. BuildKit은 Dockerfile 전체를 분석해서 의존 관계가 없는 스테이지를 자동으로 병렬 실행합니다. 코드 변경 없이 Dockerfile 구조만 그대로 두면 됩니다.

DAG 병렬 빌드와 cache-from

BuildKit의 병렬 빌드 원리

BuildKit은 Dockerfile을 파싱할 때 스테이지 간 의존 관계를 분석하여 DAG(방향성 비순환 그래프)를 구성합니다. 서로 의존하지 않는 스테이지는 자동으로 병렬 실행됩니다.

Dockerfile
# syntax=docker/dockerfile:1.6

# ── 스테이지 1: 프론트엔드 빌드 (독립적)
FROM node:20-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY frontend/ .
RUN npm run build

# ── 스테이지 2: 백엔드 빌드 (독립적, 스테이지 1과 병렬 실행)
FROM golang:1.21-alpine AS backend-builder
WORKDIR /app
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    go build -o /app/server ./cmd/server

# ── 스테이지 3: 최종 이미지 (스테이지 1, 2 완료 후 실행)
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=backend-builder /app/server .
COPY --from=frontend-builder /app/frontend/dist ./static
EXPOSE 8080
ENTRYPOINT ["/app/server"]
빌드 타임라인:
     0s ──────────────────────────────▶ 시간
     │
     ├─ frontend-builder ────────── 45s ──┐
     │                                    │
     ├─ backend-builder ──────── 40s ─────┤
     │                                    ▼
     └──────────────────────── final 10s ─▶ 완료 55s

     (직렬 실행 시: 45 + 40 + 10 = 95s)
     (병렬 실행 시: max(45,40) + 10 = 55s)

--cache-from: CI 환경의 레지스트리 캐시

CI 파이프라인은 매 실행마다 새 환경(컨테이너/VM)에서 시작하므로 로컬 레이어 캐시가 없습니다. --cache-from으로 레지스트리의 이미지를 캐시 소스로 지정합니다.

Docker
# CI 파이프라인 빌드 스크립트 (GitHub Actions 예시)

# 1단계: 레지스트리에서 기존 이미지를 캐시로 불러오면서 빌드
docker buildx build \
  --cache-from type=registry,ref=ghcr.io/myorg/myapp:buildcache \
  --cache-to   type=registry,ref=ghcr.io/myorg/myapp:buildcache,mode=max \
  --tag ghcr.io/myorg/myapp:latest \
  --push \
  .

cache-to 모드 비교

Docker
# mode=min (기본값): 최종 이미지의 레이어만 캐시에 저장
# → 캐시 크기 작음, 중간 스테이지 캐시 없음
docker buildx build \
  --cache-to type=registry,ref=myregistry/myapp:cache,mode=min .

# mode=max: 모든 중간 스테이지 레이어도 캐시에 저장
# → 캐시 크기 크지만 멀티스테이지 빌드에서 최대 효과
docker buildx build \
  --cache-to type=registry,ref=myregistry/myapp:cache,mode=max .

--progress=plain으로 빌드 단계 분석

Docker
# 빌드 단계별 소요 시간 전체 출력
docker buildx build --progress=plain -t myapp:latest . 2>&1 | tee build.log

# 출력 예시:
# #1 [internal] load build definition from Dockerfile          0.1s
# #2 [internal] load .dockerignore                             0.0s
# #3 [frontend-builder 1/5] FROM node:20-alpine               0.3s
# #4 [backend-builder 1/4] FROM golang:1.21-alpine            0.2s
# #5 [frontend-builder 2/5] COPY frontend/package*.json       0.1s
# #6 [backend-builder 2/4] COPY go.mod go.sum                 0.1s
# #7 [frontend-builder 3/5] RUN npm ci                       38.4s  ← 오래 걸리는 단계
# #8 [backend-builder 3/4] RUN go mod download               22.1s
# ...

# 가장 오래 걸리는 단계 찾기
grep -E '#[0-9]+ .* [0-9]+\.[0-9]+s$' build.log | sort -t' ' -k3 -rn | head -10

docker buildx bake — 복잡한 빌드 선언적 관리

여러 이미지를 빌드하는 복잡한 파이프라인을 docker-bake.hcl 파일로 선언적으로 관리할 수 있습니다.

HCL
# docker-bake.hcl
group "default" {
  targets = ["api", "frontend", "worker"]
}

variable "REGISTRY" {
  default = "ghcr.io/myorg"
}

variable "TAG" {
  default = "latest"
}

target "api" {
  context    = "./services/api"
  dockerfile = "Dockerfile"
  tags       = ["${REGISTRY}/api:${TAG}"]
  cache-from = ["type=registry,ref=${REGISTRY}/api:buildcache"]
  cache-to   = ["type=registry,ref=${REGISTRY}/api:buildcache,mode=max"]
}

target "frontend" {
  context    = "./services/frontend"
  dockerfile = "Dockerfile"
  tags       = ["${REGISTRY}/frontend:${TAG}"]
  cache-from = ["type=registry,ref=${REGISTRY}/frontend:buildcache"]
  cache-to   = ["type=registry,ref=${REGISTRY}/frontend:buildcache,mode=max"]
}

target "worker" {
  context    = "./services/worker"
  tags       = ["${REGISTRY}/worker:${TAG}"]
}
Docker
# 모든 타겟을 병렬로 빌드
docker buildx bake --push

# 특정 타겟만 빌드
docker buildx bake api --push

# 드라이 런으로 설정 확인
docker buildx bake --print


1--mount=type=cache로 Node.js 빌드 속도 비교

캐시 마운트 없는 기존 방식과 캐시 마운트 방식의 빌드 시간을 직접 비교합니다.

Dockerfile
# Dockerfile.nocache — 기존 방식
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
Dockerfile
# Dockerfile.cache — BuildKit 캐시 마운트 방식
# syntax=docker/dockerfile:1.6
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build
로컬 터미널
# 첫 번째 빌드 (캐시 없음, 시간 측정)
time docker buildx build --progress=plain -t node-nocache:test -f Dockerfile.nocache .

# 두 번째 빌드 (캐시 마운트, 첫 실행)
time docker buildx build --progress=plain -t node-cache:test -f Dockerfile.cache .

# package.json 내용 일부 변경 후 세 번째 빌드 (캐시 재사용 효과 확인)
time docker buildx build --progress=plain -t node-cache:test2 -f Dockerfile.cache .
docker buildx build --progress=plain -t node-nocache:test -f Dockerfile.nocache .
🔍실행 후 확인할 것
  • 세 번째 빌드에서 npm ci 단계가 첫 번째보다 현저히 빠른지 확인 (캐시 히트)
  • --progress=plain 출력에서 'CACHED' 표시가 붙은 단계가 늘어났는지 확인
  • docker buildx du 로 BuildKit 캐시 사용량 확인 (별도 캐시 스토리지 존재 검증)
  • docker images로 최종 이미지 크기 비교 — 캐시 마운트를 써도 이미지 크기가 동일한지 확인
  • docker history node-cache:test --no-trunc | grep npm — npm 토큰 등 비밀값이 노출되지 않는지 검증

트러블슈팅

증상

docker buildx build 명령을 실행하면 Dockerfile을 찾지 못한다는 에러가 납니다.

Docker
docker buildx build -t myapp:latest .
# ERROR: failed to solve: failed to read dockerfile:
# open /tmp/buildkit123/Dockerfile: no such file or directory

원인 진단

로컬 터미널
# 현재 디렉토리 확인
ls -la Dockerfile*
# (아무것도 없거나 대소문자가 다를 수 있음)

# 파일명 확인 — 'dockerfile' (소문자)인 경우
ls dockerfile  # 소문자로 작성된 경우

# buildx는 기본적으로 'Dockerfile' (대문자 D)를 찾음

해결

로컬 터미널
# 방법 1: 파일명 수정
mv dockerfile Dockerfile

# 방법 2: -f 옵션으로 파일 명시
docker buildx build -f dockerfile -t myapp:latest .

# 방법 3: --file 전체 경로 지정
docker buildx build --file ./infra/Dockerfile -t myapp:latest .

증상

로컬에서는 두 번째 빌드가 확실히 빠른데, CI(GitHub Actions)에서는 매번 처음부터 다운로드합니다.

로컬 터미널
# CI 로그 (GitHub Actions):
# #7 [3/5] RUN --mount=type=cache,target=/root/.npm npm ci
# #7 CACHED? NO — 38.4s  ← 매번 풀 설치

원인

--mount=type=cache는 BuildKit 빌드 호스트의 로컬 캐시를 사용합니다. GitHub Actions는 Job마다 새 가상 머신을 프로비저닝하므로 이전 빌드의 로컬 캐시가 존재하지 않습니다.

로컬 빌드:   항상 같은 머신 → BuildKit 캐시 유지됨 ✓
GitHub Actions: 새 VM마다 시작 → BuildKit 캐시 없음 ✗

해결

CI 환경에서는 --cache-from / --cache-to 레지스트리 캐시 전략을 병행해야 합니다.

YAML
# .github/workflows/build.yml
- name: Build and push with cache
  run: |
    docker buildx build \
      --cache-from type=registry,ref=ghcr.io/${{ github.repository }}:buildcache \
      --cache-to   type=registry,ref=ghcr.io/${{ github.repository }}:buildcache,mode=max \
      --tag ghcr.io/${{ github.repository }}:latest \
      --push \
      .
로컬 터미널
# 캐시 효과 확인 — CI 두 번째 실행 이후 로그에서 CACHED 확인
# #7 [3/5] RUN --mount=type=cache,target=/root/.npm npm ci
# #7 CACHED  0.1s  ← 레지스트리 캐시 히트!

증상

빌드 캐시 오염을 의심해서 docker builder prune --all을 실행했더니, 다음 빌드에서 모든 패키지를 처음부터 다시 내려받습니다.

Docker
docker builder prune --all --force
# 빌드 캐시 2.3 GB 삭제됨

docker buildx build -t myapp:test .
# #7 [3/5] RUN --mount=type=cache,target=/root/.npm npm ci
# #7 38.2s  ← 다시 전체 다운로드

원인 및 대처

docker builder prune은 BuildKit의 레이어 캐시와 마운트 캐시 모두를 삭제합니다. 정상 동작이지만, 캐시 오염 의심 시에는 전체 정화보다 타겟 정화를 먼저 시도하세요.

로컬 터미널
# 권장: 전체 정화 전에 오염 범위 파악
docker system df  # 캐시 크기 확인

# 단계적 정화
docker builder prune        # 24시간 이상 된 캐시만 삭제 (더 안전)
docker builder prune --all  # 전체 삭제 (마지막 수단)

# 정화 후 캐시 재워밍 (warm-up) — 첫 빌드는 느리지만 두 번째부터 정상
docker buildx build -t myapp:warmup .  # 캐시 재구축
docker buildx build -t myapp:latest .  # 이후 정상 속도

💼
실무 맥락
현업 패턴

BuildKit 캐시가 실제로 팀 생산성에 미치는 영향

중견 규모 서비스 팀에서 배포 파이프라인을 담당하게 됐을 때, CI 빌드 시간이 평균 18분이었습니다. 팀원 8명이 하루 34회 PR을 올리면 하루에 빌드 큐가 수십 건 쌓입니다. 리뷰어가 "CI 결과 기다리는 동안" 다른 PR로 넘어가고, 피드백 루프가 12시간으로 늘어납니다.

BuildKit 캐시 전략 적용 후 결과:

Before:
- npm install: 평균 9분 (의존성 340개)
- 전체 빌드: 18분
- 하루 빌드 큐 대기: 누적 2시간 이상

After (캐시 마운트 + 레지스트리 캐시):
- npm install (캐시 히트 시): 12초
- 전체 빌드: 4분 30초
- 빌드 큐 대기: 거의 없음

실무에서 자주 쓰는 패턴:

Docker
# 1. 멀티 서비스 Bake — 마이크로서비스 여러 개를 한 번에 병렬 빌드
docker buildx bake --push

# 2. 아키텍처별 멀티 플랫폼 빌드 (ARM + AMD64)
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --cache-from type=registry,ref=myregistry/app:cache \
  --cache-to   type=registry,ref=myregistry/app:cache,mode=max \
  -t myregistry/app:latest \
  --push .

# 3. 개발 빌드와 프로덕션 빌드 캐시 분리
# 같은 base 레이어 캐시를 공유하면서 최종 스테이지만 분리
docker buildx build --target=dev   --cache-to type=registry,ref=myregistry/app:dev-cache   --push .
docker buildx build --target=prod  --cache-from type=registry,ref=myregistry/app:dev-cache --push .

비용 관점 — 캐시가 CI 비용을 줄이는 이유: GitHub Actions, CircleCI 등은 빌드 시간을 기준으로 과금합니다. npm install 9분을 12초로 줄이면 월 수백 달러의 CI 비용 절감이 실제로 발생합니다.

다음 모듈에서는 Docker Swarm의 한계와 Kubernetes 전환 로드맵을 다루며, 단일 서버를 넘어 멀티 노드 오케스트레이션으로 이동하는 방법을 살펴봅니다.

지식 확인

퀴즈 — 5문제

Q1

BuildKit의 RUN --mount=type=cache 가 기존 레이어 캐시와 다른 핵심 차이점은?

Q2

RUN --mount=type=secret 을 사용하는 주된 이유는?

Q3

BuildKit의 병렬 스테이지 빌드가 가능한 이유는?

Q4

--cache-from 플래그의 올바른 사용 목적은?

Q5

docker build --progress=plain 플래그의 역할은?

0 / 5 답변

🧪 실습으로 확인하기

Docker Compose 멀티 서비스 구성

초급

docker-compose.yml로 nginx + 앱 컨테이너를 함께 정의하고, 서비스 간 통신과 볼륨 마운트를 구성한다.

35📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

docker고급 · 60
[Docker] cAdvisor + Prometheus + Grafana 모니터링 실무
Docker 트랙 계속
networking입문 · 45
[Network] OSI 7계층과 TCP/IP 4계층 모델 실무적 관점 분석
Networking 트랙 시작점