Как работает кэш слоев:
Из главы 3 вы помните, что каждая инструкция Dockerfile создает слой. При повторной сборке Docker сверяет инструкцию и ее входные файлы с прошлым результатом: если ничего не изменилось, слой берется из кэша. Но как только один слой инвалидировался, все слои ниже него пересобираются заново, кэш для них уже не действует.
Отсюда главное правило: редко меняющееся ставим выше, часто меняющееся ниже. Классическая ошибка: написать COPY . . до установки зависимостей. Тогда любая правка в коде инвалидирует копирование, и npm ci (или pip install, без разницы) отрабатывает с нуля при каждой сборке. Правильный порядок такой:
Код: Выделить всё
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]Второй обязательный шаг: файл .dockerignore в корне проекта.
Код: Выделить всё
node_modules
.git
dist
*.log
.envMulti-stage сборка:
Для сборки приложения нужны компилятор, dev-зависимости, весь тулчейн. Для запуска нужен только артефакт. Multi-stage позволяет собрать все в одном образе, а в финальный скопировать готовый результат:
Код: Выделить всё
FROM golang:1.24-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /bin/app ./cmd/app
FROM alpine:3.22
COPY --from=builder /bin/app /bin/app
USER nobody
ENTRYPOINT ["/bin/app"]Проверить результат можно командой docker image ls, а docker history покажет размер каждого слоя, сразу видно, кто раздул образ.
Почему rm не уменьшает образ:
Слои только добавляют данные. Если файл создан в одном слое, а удален в следующем, физически он остается в образе. Поэтому мусор убирают в той же инструкции RUN, где он появился:
Код: Выделить всё
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*Типичные грабли:
COPY . . первой строкой после FROM. Кэш зависимостей умирает, сборка всегда идет с нуля. Лечится порядком инструкций, как выше.
Забытый .dockerignore. Симптом: контекст сборки в сотни мегабайт (Docker пишет его размер в начале сборки) и кэш, который слетает без видимых причин.
Alpine не всегда друг. В нем musl вместо glibc, и нативные npm-модули или Python-пакеты иногда не собираются или ведут себя странно. Если уперлись, берите node:22-slim или python:3.13-slim: они на Debian, чуть тяжелее, зато без сюрпризов.
Нефиксированный тег базового образа. FROM node без тега или с тегом latest означает, что завтра сборка может пойти на другой версии. Указывайте хотя бы мажорную версию явно.
Если зависимостей много, посмотрите на кэш-монтирования BuildKit: строка RUN --mount=type=cache,target=/root/.npm npm ci сохраняет кэш пакетного менеджера между сборками, даже когда сам слой пересобирается.
Что усвоили:
Кэш слоев работает сверху вниз, поэтому порядок инструкций определяет скорость сборки. Multi-stage отделяет тулчейн от рантайма и сокращает образ в разы. Слои только добавляются, чистить надо в том же RUN, где намусорили. В следующей главе займемся уже запущенными контейнерами: логи, отладка и мониторинг.