🔥 Yarn 4: Слои Docker или почему твой билд идет 15 Минут

Вчера научился делать Dockerfile для Yarn 4, сегодня каждый коммит собирается по 15 минут? Поздравляю, ты открыл для себя мир Docker layer cache. Или точнее — мир его отсутствия.

🐕 Тест на умность собаки #1
Собака понимает, что если каждый раз приносить всю еду из магазина, это долго. Она запоминает, где лежит миска. Ты копируешь все файлы проекта перед yarn install и удивляешься, почему кэш не работает.

Как Docker кэширует слои (для тех, кто прогулял)

Docker кэширует каждый слой (команду). Если файлы в COPY изменились — все последующие слои пересобираются. Вся магия в порядке команд.

Плохо:

```
FROM node:20-alpine
COPY . . # Изменился компонент → инвалидация
RUN yarn install # Пересборка 5 минут
RUN yarn build # Пересборка 3 минуты
```

Хорошо:

```
FROM node:20-alpine
COPY package.json yarn.lock ./ # Изменились зависимости → инвалидация
RUN yarn install # Только если поменялись зависимости
COPY . . # Изменился код
RUN yarn build # Только код пересобирается
```

🐕 Тест на умность собаки #2

Собака не идет в магазин за кормом, если корм еще есть дома. Ты переустанавливаешь 800MB зависимостей, потому что поменял цвет кнопки.
Правильная организация слоев
```dockerfile
FROM node:20-alpine AS deps
RUN corepack enable && corepack prepare yarn@4.1.0 --activate
RUN apk add --no-cache python3 g++ make git
WORKDIR /app

# Отдельный stage для зависимостей
COPY package.json yarn.lock .yarnrc.yml ./
COPY .yarn/releases ./.yarn/releases
RUN yarn install --immutable

# Stage для сборки
FROM node:20-alpine AS builder
RUN corepack enable && corepack prepare yarn@4.1.0 --activate
WORKDIR /app

# Копируем установленные зависимости
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/package.json /app/yarn.lock ./

# Теперь код
COPY . .
RUN yarn build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
```

Профит: Если Docker registry поддерживает cache, stage deps кэшируется между разными ветками.

📁 .dockerignore (который ты забыл)
Создай .dockerignore в корне проекта:

node_modules
.git
.vscode
.idea
dist
build
*.log
.env.local
.DS_Store
coverage
.yarn/cache
.yarn/install-state.gz

Почему это критично:

Без .dockerignore команда COPY . . тащит весь node_modules (800MB) в build context. Docker загружает это в daemon перед сборкой. Итог: лишние 30 секунд на каждый билд.

🐕 Тест на умность собаки #3

Собака не тащит в будку весь двор. Она берет только кость. Ты без .dockerignore копируешь 2GB мусора в Docker context, потом удивляешься почему docker build тормозит на "Sending build context".

🎯 Типичные ошибки (которые точно встретишь)
Ошибка #1: Копируешь package.json, но не .yarnrc.yml

```
COPY package.json yarn.lock ./
# А где .yarnrc.yml? А где .yarn/releases?
# Получи yarn 1.22 вместо 4.x
```
Ошибка #2: Копируешь только src, забыл про конфиги

```
COPY src ./src
RUN yarn build
# Error: Cannot find tsconfig.json
# Error: Cannot find vite.config.ts
```
Ошибка #3: Кэшируешь node_modules в GitLab CI

Самая популярная ошибка — копировать подход из Yarn 1 и пытаться кэшировать node_modules.
НЕПРАВИЛЬНО (но все так делают):
```
cache:
paths:
- node_modules/ # 800MB жирного кэша
```
Почему это плохо:

node_modules весит 800MB+ → загрузка/сохранение по 2-3 минуты
Содержит симлинки и платформо-специфичные бинарники
Может сломаться при восстановлении на другом раннере
GitLab тратит время на архивацию тысяч мелких файлов

ПРАВИЛЬНО (как задумывали в Yarn 4):
yamlvariables:
YARN_CACHE_FOLDER: .yarn/cache

cache:
key:
files:
- yarn.lock
paths:
- .yarn/cache/
- .yarn/install-state.gz

Что тут происходит:
.yarn/cache — глобальный кэш Yarn 4. Содержит zip-архивы всех пакетов. Yarn сам распакует нужное в node_modules при установке. Весит меньше, качается быстрее, платформонезависим.
.yarn/install-state.gz — оптимизационный файл (не коммитится в git, но кэшируется в CI). Хранит точное состояние проекта, чтобы Yarn не резолвил workspaces заново. Экономит 10-30 секунд на каждом запуске.

🚀 GitLab CI: Продвинутый вариант

```
variables:
YARN_CACHE_FOLDER: .yarn/cache
FF_USE_FAST_ZIP: 'true'
ARTIFACT_COMPRESSION_LEVEL: 'fast'
CACHE_COMPRESSION_LEVEL: 'fast'

cache:
key:
files:
- yarn.lock
paths:
- .yarn/cache/
- .yarn/install-state.gz
policy: pull

install:
stage: install
script:
- corepack enable
- yarn install --immutable
cache:
key:
files:
- yarn.lock
paths:
- .yarn/cache/
- .yarn/install-state.gz
policy: pull-push
artifacts:
paths:
- node_modules/
expire_in: 1 hour

build:
stage: build
dependencies:
- install
script:
- yarn build
cache:
policy: pull
```
Что тут происходит:

Отдельный stage для установки зависимостей
Кэш с pull-push только в install job
Остальные jobs используют policy: pull (не перезаписывают кэш)
node_modules передается через artifacts между stages
Флаги FF_USE_FAST_ZIP ускоряют архивацию

📊 Сравнение времени сборки
Без оптимизации:

Первая сборка: 8 минут
Изменили код: 8 минут (все заново)

Кэширование node_modules:

Первый запуск: yarn install 5 мин + cache upload 3 мин = 8 мин
Второй запуск: cache download 3 мин + yarn install 1 мин = 4 мин

Кэширование .yarn/cache:

Первый запуск: yarn install 5 мин + cache upload 30 сек = 5.5 мин
Второй запуск: cache download 20 сек + yarn install 2 мин = 2.5 мин

Кэширование .yarn/cache + .yarn/install-state.gz:

Первый запуск: yarn install 5 мин + cache upload 30 сек = 5.5 мин
Второй запуск: cache download 20 сек + yarn install 1.5 мин = 2 мин

Экономия: 5 минут × 20 коммитов в день = 100 минут в день = 8 часов в неделю
⚠️ Важные нюансы про .yarn/install-state.gz

Этот файл НЕ коммитится в git (добавь в .gitignore)
Но ДОЛЖЕН кэшироваться в CI для ускорения
При восстановлении из кэша Yarn может пропустить postinstall скрипты — проверь, что ничего критичного там нет
Если используешь PnP вместо node-modules — кэшируй .yarn/cache и .pnp.cjs

Чек-лист перед коммитом
.dockerignore создан
package.json и yarn.lock копируются отдельно
.yarn/releases скопирован
Код копируется ПОСЛЕ yarn install
Multi-stage build для финального образа
В GitLab CI кэшируется .yarn/cache, не node_modules
.yarn/install-state.gz в .gitignore, но в cache CI

P.S. Если твой коллега говорит "у меня работает быстро" — проверь, не использует ли он --no-cache в docker build. Некоторые люди просто хотят смотреть, как горит мир.

Я ждал этой информации от фронтов слишком долго - пришлось учить yarn (