테마 전환

Docker 빌드 가속: 캐시를 활용해 빌드 속도를 10배 높이는 실전 가이드

Easton editorial illustration: registry transfer crane

오타 하나를 고치고 다시 docker build를 실행했더니, 이런—npm install이 또 시작됐습니다. 10분이 지나도록 소셜 미디어 피드를 스무 번쯤 넘겨 보고 커피도 두 잔이나 마셨는데 진행 표시줄은 여전히 돌아가고 있었습니다.

컨테이너 기반 개발을 해 본 사람이라면 이 답답함을 잘 알 겁니다.

Docker 캐시 구조를 파고든 끝에 빌드 시간을 10분에서 30초로 줄였습니다.

30초
빌드 시간
10분에서 30초로 단축, 20배 향상

이 글에서는 당장 적용할 수 있는 세 가지 방법, 즉 .dockerignore 설정, 레이어 캐시 원리 이해, Dockerfile 명령 순서 최적화를 다룹니다. 마지막에는 한 단계 더 나아가 BuildKit 캐시 마운트도 살펴봅니다. 빌드가 느리다면 계속 읽어 보세요.

Docker 빌드는 왜 이렇게 느릴까요?

빌드 컨텍스트가 너무 큽니다

먼저 많은 사람이 한 번쯤 겪는 함정인 **빌드 컨텍스트(Build Context)**부터 이야기해 보겠습니다.

docker build .을 실행하면 Docker가 가장 먼저 하는 일은 Dockerfile을 실행하는 것이 아닙니다. . 디렉터리 아래의 모든 파일을 묶어서 Docker daemon으로 전송합니다. 말 그대로 전부입니다. node_modules도, .git 폴더도, 다운로드한 수백 MB의 테스트 데이터도 포함됩니다.

제가 본 가장 심한 사례에서는 한 프론트엔드 프로젝트의 빌드 컨텍스트가 800MB나 됐습니다. 이 파일을 전송하는 데만 2~3분이 걸렸습니다. 하지만 이미지에 실제로 필요한 소스 코드는 10MB도 되지 않았습니다.

책 한 권을 택배로 보내려다가 책장까지 함께 포장한 셈입니다.

레이어 캐시 무효화의 연쇄 반응

두 번째 문제는 Docker의 레이어 캐시 구조를 이해하지 못하는 데서 생깁니다.

Docker 이미지는 여러 레이어로 구성됩니다. Dockerfile의 각 명령인 FROM, RUN, COPY는 각각 하나의 레이어를 만듭니다. Docker는 빌드할 때 각 레이어에 사용할 수 있는 캐시가 있는지 확인합니다. 명령이 완전히 같고 관련 파일도 바뀌지 않았다면 다시 실행하지 않고 캐시를 바로 사용합니다.

듣기에는 아주 좋아 보이죠?

하지만 어느 한 레이어의 캐시가 무효화되면 이후의 모든 레이어를 다시 빌드해야 합니다. 첫 도미노를 쓰러뜨리면 뒤의 도미노가 모두 넘어지는 것과 같습니다.

많은 사람이 Dockerfile을 다음과 같이 작성합니다.

FROM node:18
COPY . /app
WORKDIR /app
RUN npm install

문제가 없어 보이나요? 사실 큰 문제가 있습니다.

COPY . /app은 프로젝트 전체를 복사합니다. README.md의 오타 하나만 고쳤더라도 어떤 파일이든 변경되면 이 레이어의 캐시는 무효화됩니다. 그러면 뒤에 있는 npm install도 다시 실행해야 합니다.

코드 한 줄을 수정했을 뿐인데 전체 의존성 트리를 다시 설치하게 되는 이유가 바로 이것입니다.

명령 순서가 적절하지 않습니다

세 번째 함정은 명령 순서를 어떻게 정해야 하는지 모르는 것입니다.

Docker의 캐시 전략은 간단합니다. 위에서 아래로 차례로 확인하고, 한 번 무효화되면 캐시 사용을 중단합니다. 따라서 자주 바뀌지 않는 명령은 앞에, 자주 바뀌는 명령은 뒤에 배치해야 합니다.

하지만 실제로는 많은 Dockerfile이 반대로 작성돼 있습니다. 가장 자주 바뀌는 코드를 먼저 복사하고, 자주 바뀌지 않는 의존성을 나중에 설치합니다. 그 결과 코드를 수정할 때마다 의존성 캐시가 모두 무효화됩니다.

결국 무엇이 자주 바뀌고 무엇이 비교적 안정적인지 구분하지 못해서 생기는 문제입니다.

방법 1 - .dockerignore를 설정해 빌드 컨텍스트 줄이기

문제가 무엇인지 확인했으니 가장 간단하고 즉시 효과를 볼 수 있는 최적화인 .dockerignore부터 살펴보겠습니다.

.dockerignore란 무엇인가요?

.gitignore를 사용해 본 적이 있나요? .dockerignore도 같은 역할을 합니다. 어떤 파일을 빌드 컨텍스트에 포함하지 않을지 Docker에 알려 줍니다.

만드는 방법은 아주 간단합니다. 프로젝트 루트(Dockerfile과 같은 위치)에 .dockerignore 파일을 만든 뒤 규칙을 작성하면 됩니다.

Node.js 프로젝트에서는 어떻게 설정하나요?

바로 예제를 보겠습니다. 제가 실제로 사용하는 설정입니다.

# 의존성 디렉터리
**/node_modules/
**/npm-debug.log
**/.npm

# Git 관련 파일
.git/
.gitignore
.gitattributes

# 테스트와 문서
**/test/
**/tests/
**/docs/
**/*.md
!README.md

# IDE와 편집기
.vscode/
.idea/
*.swp
*.swo
.DS_Store

# 환경 변수와 설정
.env
.env.*
*.local

# 빌드 결과물
dist/
build/
coverage/

몇 가지 핵심 사항이 있습니다.

  1. node_modules는 반드시 제외해야 합니다. 수백 MB에 이를 수 있는 데다 이미지 안에서 다시 설치하므로 로컬 파일을 복사할 필요가 없습니다.
  2. **/ 접두사로 중첩된 모든 디렉터리를 일치시키세요. 예를 들어 **/node_modules/./node_modules/./packages/lib/node_modules/를 모두 찾아 빠뜨리지 않습니다.
  3. 디렉터리에는 슬래시를 붙이세요. node_modules/는 디렉터리를, node_modules는 파일을 뜻합니다. 비슷해 보여도 Docker는 이를 엄격히 구분합니다.

효과는 얼마나 뚜렷한가요?

Next.js 프로젝트에서 직접 테스트해 봤습니다.

  • 설정 전: 빌드 컨텍스트 520MB, 전송 시간 2분 15초
  • 설정 후: 빌드 컨텍스트 4.8MB, 전송 시간 3초

맞습니다. 단 3초입니다. 한 번에 2분을 아꼈습니다.

이미지 크기가 줄어든 효과는 여기에 포함하지도 않았습니다. .git과 node_modules를 더 이상 이미지에 넣지 않으면서 최종 이미지 크기도 1.2GB에서 680MB로 줄었습니다.

흔한 함정

함정 1: .dockerignore는 빌드 컨텍스트의 루트 디렉터리에 있을 때만 적용됩니다. 빌드 명령이 docker build -f subfolder/Dockerfile .이라면 .dockerignore는 subfolder가 아니라 프로젝트 루트에 두어야 합니다.

함정 2: node_modules처럼 슬래시 없이 작성하면 적용되지 않을 수 있습니다. node_modules/처럼 슬래시를 붙이세요.

함정 3: .git을 제외하는 것을 잊기 쉽습니다. .git 디렉터리는 수백 MB에 이를 수 있지만 빌드에는 전혀 쓰이지 않습니다.

방법 2 - Docker 레이어 캐시 구조를 이해하고 활용하기

.dockerignore는 전송 속도 문제를 해결하지만, 핵심은 캐시가 어떻게 작동하는지 이해하는 것입니다.

레이어 캐시는 정확히 어떻게 작동하나요?

Docker 이미지는 여러 겹의 케이크와 같습니다. 각 레이어는 Dockerfile 명령 하나를 실행한 결과입니다.

다음 Dockerfile을 예로 들어 보겠습니다.

FROM node:18          # 레이어 1
RUN apt-get update    # 레이어 2
COPY package.json .   # 레이어 3
RUN npm install       # 레이어 4
COPY . .              # 레이어 5

빌드할 때 Docker는 레이어를 하나씩 확인합니다.

  1. 레이어 1: FROM 명령에서 로컬에 node:18 이미지가 있는지 확인합니다. 있다면 캐시를 사용합니다.
  2. 레이어 2: RUN 명령에서 명령 문자열이 같은지 확인합니다. 같다면 캐시를 사용합니다.
  3. 레이어 3: COPY 명령에서 package.json의 체크섬(checksum)을 계산합니다. 파일이 바뀌지 않았다면 캐시를 사용합니다.
  4. 레이어 4: RUN 명령을 계속 확인합니다.
  5. 레이어 5: 같은 방식으로 확인합니다.

핵심은 어느 한 레이어의 캐시가 무효화되면 이후의 모든 레이어를 다시 빌드해야 한다는 점입니다.

앞에서 말한 도미노 효과가 바로 이것입니다. 레이어 3에서 package.json을 수정하면 레이어 4의 npm install과 레이어 5의 코드 복사를 모두 다시 실행해야 합니다.

캐시가 적용됐는지 어떻게 확인하나요?

빌드 출력을 확인하세요.

Step 3/5 : COPY package.json .
 ---> Using cache
 ---> 3a8f29e7c5b1

Using cache가 보이면 캐시가 적용된 것입니다. 보이지 않는다면 다시 빌드하고 있다는 뜻입니다.

docker history <이미지 ID>로 이미지 레이어 기록을 확인할 수도 있습니다. 각 레이어의 SIZE와 생성 시간을 한눈에 볼 수 있습니다.

COPY 명령은 왜 특별한가요?

RUN 명령은 명령 문자열만 확인합니다. RUN npm install은 문자열이 바뀌지 않으면 Docker가 캐시를 사용할 수 있다고 판단합니다.

하지만 COPY와 ADD는 다릅니다. Docker는 복사할 파일 내용의 체크섬을 계산합니다. 파일 이름이 같더라도 내용이 바뀌면 캐시가 무효화됩니다.

파일 내용이 바뀌면 이후 빌드 단계가 영향을 받을 수 있으므로 이전 캐시를 그대로 사용할 수 없다는 점에서 영리한 설계입니다.

하지만 이 때문에 COPY . . 같은 작성 방식은 특히 위험합니다. 프로젝트의 어떤 파일이든, README.md 하나라도 바뀌면 이 레이어의 캐시가 무효화됩니다.

방법 3 - Dockerfile 명령 순서 최적화하기

캐시 원리를 이해했다면 이제 실전에 적용할 차례입니다. 캐시를 최대한 활용하려면 Dockerfile을 어떻게 작성해야 할까요?

황금률: 안정적인 것부터 자주 바뀌는 것 순으로

핵심은 한 문장으로 정리할 수 있습니다. 자주 바뀌지 않는 명령은 앞에, 자주 바뀌는 명령은 뒤에 배치하세요.

왜 그럴까요? Docker는 위에서 아래로 캐시를 확인하기 때문입니다. 앞쪽 레이어가 안정적이면 뒤쪽이 자주 바뀌어도 앞쪽 캐시에는 영향을 주지 않습니다.

구체적인 순서는 다음과 같습니다.

  1. 기본 이미지 - 거의 바뀌지 않음
  2. 시스템 의존성 - 가끔 바뀜
  3. 프로젝트 의존성 - 때때로 바뀜
  4. 소스 코드 - 매일 바뀜

이 순서로 배치하면 캐시 활용률을 극대화할 수 있습니다.

잘못된 예: 코드를 먼저 복사하고 의존성 설치하기

처음에는 많은 사람이 다음과 같이 작성합니다.

FROM node:18
WORKDIR /app

# 잘못된 방식: 프로젝트 전체를 바로 복사
COPY . .

# 그다음 의존성 설치
RUN npm install

# 시작 명령
CMD ["npm", "start"]

무엇이 문제일까요? 소스 코드를 수정하면 COPY . . 레이어의 캐시가 무효화되고, 뒤에 있는 npm install도 다시 실행해야 합니다.

결국 JavaScript 코드 한 줄을 수정했을 뿐인데 npm 패키지 수백 개를 다시 설치합니다. 또 10분을 기다리게 됩니다.

올바른 예: 의존성을 먼저 설치하고 코드 복사하기

최적화한 작성 방식은 다음과 같습니다.

FROM node:18
WORKDIR /app

# 1단계: 의존성 파일만 복사
COPY package.json package-lock.json ./

# 2단계: 의존성 설치(이 레이어가 캐시됨)
RUN npm ci --only=production

# 3단계: 소스 코드 복사
COPY . .

# 시작 명령
CMD ["npm", "start"]

이 방식에는 다음과 같은 장점이 있습니다.

  1. package.json이 바뀌지 않으면 npm ci 레이어가 캐시를 사용합니다.
  2. 소스 코드를 수정해도 COPY . . 레이어만 무효화되고 의존성 설치 캐시는 남습니다.
  3. 두 번째 빌드부터는 npm 설치를 바로 건너뛰므로 속도가 크게 빨라집니다.

직접 테스트했을 때 이 조정만으로 이후 빌드 시간이 7~8분에서 약 30초로 줄었습니다.

다른 언어도 원리는 같습니다

Python 프로젝트:

FROM python:3.11
WORKDIR /app

# requirements.txt 먼저 복사
COPY requirements.txt .

# 그다음 pip install 실행
RUN pip install --no-cache-dir -r requirements.txt

# 마지막으로 코드 복사
COPY . .

Go 프로젝트:

FROM golang:1.21
WORKDIR /app

# go.mod와 go.sum 먼저 복사
COPY go.mod go.sum ./

# 의존성 다운로드
RUN go mod download

# 그다음 코드 복사
COPY . .

# 컴파일
RUN go build -o main .

핵심 원리는 모두 같습니다. 의존성 관리 파일과 소스 코드를 따로 복사해 의존성 설치 단계의 캐시를 최대한 재사용하는 것입니다.

고급 기법: 세분화한 COPY

프로젝트 구조가 복잡하다면 COPY 단계를 더 세밀하게 나눌 수도 있습니다.

# 자주 바뀌지 않는 설정 파일 먼저 복사
COPY .eslintrc.json .prettierrc ./

# 그다음 의존성 파일 복사
COPY package*.json ./
RUN npm install

# 공용 라이브러리가 있다면 이어서 복사
COPY ./lib ./lib

# 마지막으로 비즈니스 코드 복사
COPY ./src ./src

흔히 쓰이는 방식은 아니지만 monorepo 프로젝트 같은 특정 상황에서는 매우 유용합니다.

고급 기법 - BuildKit 캐시 마운트

앞에서는 기본 최적화를 다뤘습니다. 이제 한 단계 더 나아가 BuildKit의 캐시 마운트를 살펴보겠습니다.

BuildKit이란 무엇인가요?

BuildKit은 Docker 18.09에서 도입된 새로운 빌드 엔진입니다. 기존 엔진보다 훨씬 빠르고 더 강력한 캐시 기능을 지원합니다.

활성화 방법은 아주 간단합니다.

# 일시적으로 활성화
export DOCKER_BUILDKIT=1
docker build .

# 또는 명령 앞에 바로 추가
DOCKER_BUILDKIT=1 docker build .

Docker 버전이 충분히 새롭다면(19.03 이상) BuildKit이 기본으로 활성화돼 있을 것입니다. 확실하지 않다면 docker version을 실행해 확인하세요.

캐시 마운트란 무엇인가요?

앞에서 설명한 레이어 캐시에는 한 가지 문제가 있습니다. 레이어가 무효화되면 작업 전체를 다시 실행해야 합니다.

예를 들어 package.json을 수정해 새 의존성을 추가하면 npm install 레이어의 캐시가 사라집니다. 그러면 이전에 이미 다운로드한 패키지를 포함해 모든 패키지를 다시 다운로드해야 합니다.

캐시 마운트는 이 문제를 해결하기 위한 기능입니다. 레이어 캐시가 무효화돼도 패키지 관리자의 다운로드 캐시는 유지할 수 있습니다.

쉽게 말해 패키지 관리자에 영구 캐시 디렉터리를 제공해 여러 빌드에서 공유하도록 하는 방식입니다.

어떻게 사용하나요?

Node.js 프로젝트 예시는 다음과 같습니다.

FROM node:18
WORKDIR /app

COPY package*.json ./

# 핵심: npm 캐시 디렉터리 마운트
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

COPY . .
CMD ["npm", "start"]

핵심은 --mount=type=cache,target=/root/.npm입니다.

  • type=cache는 캐시 마운트라는 뜻입니다.
  • target=/root/.npm은 npm의 캐시 디렉터리입니다.

이 설정을 적용하면 package.json이 바뀌어 레이어 캐시가 무효화되더라도 npm이 모든 패키지를 처음부터 다시 다운로드할 필요가 없습니다. /root/.npm에 있는 기존 캐시를 읽고 새로 추가되거나 업데이트된 패키지만 다운로드합니다.

다른 패키지 관리자는 어떻게 설정하나요?

Yarn:

RUN --mount=type=cache,target=/root/.yarn \
    yarn install --frozen-lockfile

pip (Python):

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

apt (시스템 패키지):

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    apt-get update && apt-get install -y gcc

apt의 sharing=locked 매개변수에 주의하세요. apt는 캐시에 독점적으로 접근해야 하므로 이 매개변수를 추가해 동시 빌드 충돌을 방지해야 합니다.

효과는 어떤가요?

의존성이 200개 이상인 프로젝트에서 테스트해 봤습니다.

  • 레이어 캐시는 무효화됐지만 캐시 마운트는 적용된 경우: 의존성 설치 시간이 8분에서 1분 30초로 단축
  • 완전한 콜드 스타트(캐시가 전혀 없는 경우): 여전히 8분

즉, 캐시 마운트는 레이어 캐시의 두 번째 방어선입니다. 레이어 캐시가 유효할 때가 가장 빠르지만(해당 단계를 바로 건너뜀), 무효화되더라도 캐시 마운트가 버텨 주므로 적어도 모든 패키지를 처음부터 다시 다운로드할 필요는 없습니다.

주의 사항

  1. 기본 보관 기간이 길지 않습니다. BuildKit은 기본적으로 2일이 넘고 512MB를 초과하는 캐시를 정리합니다. CI/CD 환경에서 사용한다면 정책을 조정해야 할 수 있습니다.

  2. 모든 상황에 필요한 것은 아닙니다. 프로젝트의 의존성이 적다면(예: 패키지 열몇 개) 캐시 마운트 적용 여부에 따른 차이가 크지 않습니다.

  3. 경로가 정확해야 합니다. 패키지 관리자마다 캐시 디렉터리가 다르므로 문서에서 확인해야 합니다.

결론

내용이 많았지만 핵심은 세 가지입니다.

첫 번째, 지금 바로 실행하세요. 프로젝트 루트에 .dockerignore 파일을 만들고 node_modules, .git, 테스트 파일 등을 제외하세요. 5분도 걸리지 않으며 빌드 컨텍스트를 90% 이상 줄일 수 있습니다.

두 번째, 오늘 바로 바꾸세요. Dockerfile 명령 순서를 조정하세요. 먼저 의존성 파일을 COPY하고, RUN으로 설치한 뒤, 마지막에 소스 코드를 COPY합니다. 이 조정만으로 이후 빌드 시간을 10분에서 30초로 줄일 수 있습니다.

세 번째, 시간이 날 때 살펴보세요. 프로젝트의 의존성이 많고 자주 변경된다면 BuildKit 캐시 마운트를 사용해 보세요. 레이어 캐시가 무효화됐을 때 큰 도움이 됩니다.

제 프로젝트에 이 세 단계를 적용한 뒤 빌드 시간이 10분에서 30초로 줄었고, 이미지 크기도 1.2GB에서 680MB로 작아졌습니다. 솔직히 말해 지금까지 해 본 최적화 중 투자 대비 효과가 가장 큰 편이었습니다.

Docker 빌드가 느린가요? 그렇다면 이 글의 방법을 따라 해 보세요. 문제가 해결되면 댓글로 결과를 알려 주세요. 얼마나 빨라졌는지 궁금합니다.

Docker 빌드를 가속하는 전체 최적화 과정

레이어 캐시, .dockerignore 설정, Dockerfile 최적화 기법을 익혀 빌드 시간을 10분에서 30초로 줄이는 방법

Estimated time: PT30M

  1. 1

    Step 1: 빌드가 느린 원인 이해하기: 빌드 컨텍스트와 레이어 캐시

    빌드 컨텍스트 문제:
  2. 2

    Step 2: 방법 1: .dockerignore를 설정해 빌드 컨텍스트 줄이기

    지금 바로 해보세요. 프로젝트 루트에 .dockerignore 파일을 만들고 node_modules, .git, 테스트 파일 등을 제외하면 됩니다. 5분도 걸리지 않으며 빌드 컨텍스트를 90% 이상 줄일 수 있습니다.
  3. 3

    Step 3: 방법 2: Dockerfile 명령 순서 최적화하기

    오늘 바로 바꿔 보세요. Dockerfile 명령 순서를 조정해 먼저 의존성 파일을 COPY하고, RUN으로 설치한 뒤, 마지막에 소스 코드를 COPY하세요. 이 조정만으로 이후 빌드 시간을 10분에서 30초로 줄일 수 있습니다.
  4. 4

    Step 4: 방법 3: BuildKit 캐시 마운트 사용하기

    시간이 날 때 살펴보세요. 프로젝트의 의존성이 많고 자주 변경된다면 BuildKit 캐시 마운트를 사용해 볼 수 있습니다. 레이어 캐시가 무효화됐을 때 큰 도움이 됩니다.

FAQ

Docker 빌드는 왜 이렇게 느린가요?
빌드 컨텍스트 문제:
• docker build .을 실행하면 Docker는 가장 먼저 . 디렉터리 아래의 모든 파일을 묶어 Docker daemon으로 전송합니다.
• 여기에는 node_modules, .git 폴더, 테스트 데이터 등이 포함됩니다.
• 프론트엔드 프로젝트의 빌드 컨텍스트가 800MB에 이를 수 있고, 이 파일을 전송하는 데만 2~3분이 걸릴 수 있습니다.
• 하지만 이미지에 실제로 필요한 소스 코드는 10MB도 되지 않을 수 있습니다.
• 책 한 권을 택배로 보내려다가 책장까지 함께 포장하는 것과 같습니다.

레이어 캐시 무효화의 연쇄 반응:
• Docker 이미지는 레이어로 구성되며, Dockerfile의 각 명령(FROM, RUN, COPY)이 하나의 레이어를 만듭니다.
• Docker는 빌드할 때 각 레이어에 사용 가능한 캐시가 있는지 확인하고, 명령이 완전히 같고 관련 파일도 바뀌지 않았다면 캐시를 바로 사용합니다.
• 하지만 어느 한 레이어의 캐시가 무효화되면 이후의 모든 레이어를 다시 빌드해야 합니다. 첫 도미노를 쓰러뜨리면 뒤의 도미노가 모두 넘어지는 것과 같습니다.

많은 사람이 Dockerfile을 FROM node:18, COPY . /app, WORKDIR /app, RUN npm install 순으로 작성합니다. 이렇게 하면 코드를 수정할 때마다 npm install이 다시 실행됩니다.
.dockerignore를 설정해 빌드 컨텍스트를 줄이려면 어떻게 해야 하나요?
지금 바로 해보세요. 프로젝트 루트에 .dockerignore 파일을 만들고 node_modules, .git, 테스트 파일 등을 제외하면 됩니다. 5분도 걸리지 않으며 빌드 컨텍스트를 90% 이상 줄일 수 있습니다.

.dockerignore 설정 예시:
• node_modules(의존성 패키지 제외)
• .git(Git 기록 제외)
• *.log(로그 파일 제외)
• .env(환경 변수 파일 제외)
• dist(빌드 결과물 제외)
• test(테스트 파일 제외)
• *.md(문서 파일 제외)

설정을 마치면 빌드 컨텍스트가 800MB에서 10MB 미만으로 줄고, 전송 시간도 2~3분에서 몇 초로 단축됩니다.
Dockerfile 명령 순서는 어떻게 최적화하나요?
오늘 바로 바꿔 보세요. Dockerfile 명령 순서를 조정해 먼저 의존성 파일을 COPY하고, RUN으로 설치한 뒤, 마지막에 소스 코드를 COPY하세요. 이 조정만으로 이후 빌드 시간을 10분에서 30초로 줄일 수 있습니다.

최적화 원칙:
• 변경 빈도가 낮은 명령을 앞에 배치합니다(FROM, 시스템 의존성 설치, 애플리케이션 의존성 설치).
• 변경 빈도가 높은 명령을 뒤에 배치합니다(소스 코드 COPY).

최적화 전:
• FROM node:18
• COPY . /app
• WORKDIR /app
• RUN npm install
• 코드를 수정할 때마다 npm install이 다시 실행됩니다.

최적화 후:
• FROM node:18
• WORKDIR /app
• COPY package*.json ./
• RUN npm install
• COPY . .
• package.json이 바뀔 때만 npm install이 다시 실행되며, 코드 수정은 의존성 설치에 영향을 주지 않습니다.
BuildKit 캐시 마운트는 어떻게 사용하나요?
시간이 날 때 살펴보세요. 프로젝트의 의존성이 많고 자주 변경된다면 BuildKit 캐시 마운트를 사용해 볼 수 있습니다. 레이어 캐시가 무효화됐을 때 큰 도움이 됩니다.

BuildKit 캐시 마운트:
• --mount=type=cache로 캐시 디렉터리를 마운트합니다.
• npm install의 node_modules를 호스트에 캐시합니다.
• 다음 빌드에서 캐시를 바로 사용해 빌드 속도를 10배 이상 높입니다.

예시:
• npm 예시: RUN --mount=type=cache,target=/root/.npm npm install
• yarn 예시: RUN --mount=type=cache,target=/root/.yarn yarn install --frozen-lockfile
• pip 예시: RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
• apt 예시: RUN --mount=type=cache,target=/var/cache/apt,sharing=locked apt-get update && apt-get install -y gcc

apt에는 sharing=locked 매개변수가 필요합니다. apt가 캐시에 독점적으로 접근해야 하므로 이 매개변수로 동시 빌드 충돌을 방지해야 합니다.
Docker 빌드를 최적화하면 효과가 어느 정도인가요?
최적화 효과:
• 빌드 시간이 10분에서 30초로 단축됩니다(20배 향상).
• 이미지 크기가 1.2GB에서 680MB로 줄어듭니다.
• 빌드 컨텍스트가 800MB에서 10MB 미만으로 줄어듭니다.
• 전송 시간이 2~3분에서 몇 초로 단축됩니다.

제 프로젝트에 이 세 단계를 적용한 뒤 빌드 시간이 10분에서 30초로 줄었고, 이미지 크기도 1.2GB에서 680MB로 작아졌습니다. 솔직히 말해 지금까지 해 본 최적화 중 투자 대비 효과가 가장 큰 편이었습니다.

핵심은 세 가지입니다.
• 첫 번째는 지금 바로 실행합니다(프로젝트 루트에 .dockerignore 파일 만들기).
• 두 번째는 오늘 바로 바꿉니다(Dockerfile 명령 순서 조정하기).
• 세 번째는 시간이 날 때 살펴봅니다(프로젝트의 의존성이 많고 자주 변경된다면 BuildKit 캐시 마운트 사용하기).

2분 읽기 · 게시일: 2025년 12월 17일 · 수정일: 2026년 9월 4일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog