Охота за тиками: как сократить сборку Docker-образа в GitHub Actions с 9 минут до 90 секунд

Пятница, 23:11. Джун-дев Кира в ярости: CI крутит job build-and-push уже девятый раз — каждый прогон тянется 9 минут. В чате горит «🔥 срочный релиз», а тикеты закрываются медленнее, чем кофе остывает.
Кира надевает плащ детектива и идёт по следу «утраченных секунд». Её цель — вычислить, где прячутся лишние слои Docker и почему GitHub Actions ленится.

1. Осмотр места преступления

  1. Фиксируем улики — самый медленный шаг: docker buildx build \ --platform linux/amd64,linux/arm64 \ --push -t ghcr.io/acme/my-app:latest . Старт-финиш: 9 мин 08 сек.
  2. Подозрение: GitHub-раннер каждый раз компилирует весь frontend и устанавливает NPM-зависимости.

2. План расследования

«Если преступник оставляет отпечатки пальцев, значит, есть кэш, который мы не используем». — Стив БилдКит, вымышленный инспектор.

Наша стратегия:

  1. Настроить Buildx-builder c включённым BuildKit.
  2. Сказать BuildKitу хранить слои в cache-backend type=gha — встроенный кэш GitHub Actions.
  3. Сохранить node_modules в отдельный actions/cache.
  4. Включить inline-cache в итоговом образе, чтобы локальные машины и другие CI тоже могли reuse слои.

3. Шаг-за-шагом: создаём workflow

3.1 Подготавливаем builder

name: ci

on:
push:
branches: [main]

jobs:
docker:
runs-on: ubuntu-22.04

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU (для multi-arch)
uses: docker/setup-qemu-action@v3

- name: Set up Buildx
id: buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=moby/buildkit:v0.13 # >=0.13 = поддержка cache v2 :contentReference[oaicite:2]{index=2}

setup-buildx-action ро­жда­ет изо­ли­ро­ван­ный builder-контейнер; кэш слоёв теперь можно экспортировать без root-прав.

3.2 Кэшируем node_modules (фронтенд)

      - name: Cache NPM deps
uses: actions/cache@v4
with:
path: |
**/node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-

Так мы экономим ≈ 40–60 с даже до начала сборки.

3.3 Кэш Docker-слоёв BuildKit

      - name: Build & push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/acme/my-app:latest
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: false # чуть ускоряем build, если SBOM не нужен
outputs: type=image,name=ghcr.io/acme/my-app:latest,push=true,cache-from=type=registry,ref=ghcr.io/acme/my-app:cache
  • type=gha — GitHub-встроенное хранилище; не занимает место в контейнерном реестре.
  • mode=max — сохраняет все слои; для pet-проекта достаточно.
  • inline-cache (cache-from=registry) даёт разработчикам локальный «холодный» старт.

4. Первый прогон: репортаж с места

ИтерацияДлительность job
baseline (без кэша)9 мин 08 с
+ NPM cache6 мин 17 с
+ BuildKit GHA cache1 мин 32 с

Экономия ~83 % за счёт двух строчек cache-from/to.


5. Что может пойти не так и как чинить

СимптомВероятная причинаФикс
error: failed to solve: no available cacheНовый ключ кэшаУбедись, что key: стабилен; зависит от lock-файла.
no space left on deviceCache-tarball > 5 ГБДобавь --build-arg BUILDKIT_INLINE_CACHE=1 и чисти кэш type=gha,mode=min.
Buildx жалуется на версиюBuildx < 0.21Укажи image=moby/buildkit:v0.13 при setup-buildx.

6. Усиляем расследование

  • Параллельные builder-нэймспейсы: build-with: | name=my-app-${{ github.run_id }} Позволяет одновременно катить несколько PR без конфликтов кэша.
  • Secret mounts: ENV-переменные --secret id=sentry,env=SENTRY_AUTH_TOKEN передаются BuildKit-у безопасно — их нет в итоговом образе.
  • Мониторинг кэша: docker buildx du локально покажет, сколько МБ «седых» слоёв можно подчистить.

7. Финальный акт

Кира запускает workflow — зелёные чеки через 90 секунд. Релиз выходит вовремя, кофе остаётся тёплым, а начальник задаётся вопросом, как быстро повысить столь ценного детектива.

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *