Розробка Multi-Stage Dockerfile для зменшення розміру образу

Образи розростаються, розгортання сповільнюється, і релізи чекають годинами. Ми проєктуємо та перебудовуємо multi-stage Dockerfile під ключ: розділяємо збірку на етапи й скорочуємо розмір образу в рази. Обговоримо завдання і повернемося з оцінкою та планом.
Часті запитання

Multi-Stage Dockerfile: де образ роздувається і гальмує викатку

Типове складання відбувається в одному етапі: базовий образ, встановлення компілятора, пакетного менеджера, заголовних файлів і утиліт для нативних модулів, копіювання вихідних кодів і кешу залежностей.

Усе це залишається у фінальному шарі, навіть якщо в кінці Dockerfile стоїть очищення. Видалення файлу в наступному шарі не зменшує розмір образу: шар незмінний, а rm лише дописує мітку зверху, тоді як самі байти продовжують лежати в батьківському шарі.

Звідси два наслідки. Розмір образу зростає в рази щодо рантайму: компілятори, налагоджувальні символи, тестові бібліотеки й сервісні утиліти — це зайві залежності в рантаймі, які не беруть участі в роботі застосунку, але тягнуться на кожен вузол.

Час складання зростає з іншої причини: правка в коді вище встановлення залежностей скидає кеш, і пакети ставляться заново на кожному прогоні.

Викатка гальмує не через код, а через транспорт: кожен вузол витягує образ повністю, контейнер довше виходить у готовність, автомасштабування під навантаженням запізнюється.

Додатково розширюється поверхня атаки — компілятор, налагоджувач і пакетний менеджер у рантаймі дають готовий набір інструментів тому, хто отримав доступ усередину контейнера.

Multi-stage dockerfile розділяє ці ролі: один етап збирає артефакт, другий отримує лише його та мінімальне середовище запуску. У фінальний шар не потрапляють ні складальні інструменти, ні кеш — розбираємо, як це влаштовано і що дає на практиці.

Що дає розділення складання на етапи: час, шари, відкати

Розділення складання на етапи (multi-stage Dockerfile) прибирає з фінального образу все, що потрібно лише для компіляції: компілятори, заголовні файли, менеджери пакетів і проміжні артефакти.

Залишається тільки те, що потрібно для запуску: зібраний застосунок і його бібліотеки часу виконання. На практиці з образу йде 60–90% вмісту — це десятки пакетів, яких у робочому контейнері просто не повинно бути.

Менше пакетів — менша поверхня атаки: складальні залежності перестають потрапляти в робочий контейнер разом зі своїми уразливостями. Менший розмір — швидша викатка: вузол забирає образ з реєстру за секунди, а не хвилини, і перезапуск сервісу вкладається у вікно оновлення.

Відтворюваність тримається на двох речах: базовий образ фіксується за digest, а порядок інструкцій вибудовується так, щоб спочатку йшли рідко змінювані шари — залежності, потім код.

Docker перевикористовує кеш шарів, поки не змінився вхід: правка одного файлу перезбирає один шар, а не весь образ. Типове перезбирання після коміту займає секунди проти хвилин в одноетапному складанні.

Для відкатів це дає передбачуваність: попередній образ залишається в реєстрі з попереднім тегом, повернення — перемикання тегу й перезапуск, без повторного складання.

Клас відмов «викотили не той стан вихідних кодів, що перевіряли» зникає: перевірений артефакт і є артефакт у проді.

Порівняння підходів: звичайний Dockerfile і multi-stage складання

Вибір між multi-stage dockerfile і складанням в один етап — не про «правильно й неправильно», а про те, що саме ви оптимізуєте: швидкість ітерації, розмір образу чи відтворюваність.

Складання в один етап — те, з чого починають майже всі проєкти на Docker, і нижче видно, на якому кроці воно перестає бути вигідним.

Варіант Коли підходить Чим обмежений
Складання в один етап Невеликий сервіс, залежності з готових пакетів У фінальний образ потрапляють компілятори й кеш пакетного менеджера: розмір зростає, разом з ним — поверхня атаки
Multi-stage Dockerfile Компільований стек, важливі розмір образу й швидкість викатки Артефакти потрібно явно переносити між етапами, а кеш залежностей — виносити окремим шаром
Окремий образ складання Кілька сервісів зі спільним тулчейном, ізольований контур Образи складання треба версіонувати й оновлювати — це окремий регулярний процес
Складання на стороні складального агента Локальні правки, швидкий цикл розробки Результат залежить від середовища агента, відтворюваність складання нижча

Ключовий ефект multi-stage — робота з кешем залежностей. Шар зі встановленням пакетів ставиться до копіювання вихідних кодів, тому правка коду не скидає його: повторне складання відбувається за секунди, а не заново.

У фінальний образ потрапляють лише артефакти й рантайм — службові утиліти та вихідні коди залишаються в проміжному етапі.

Обмеження теж реальні. Якщо базовий рантайм тягне зайві бібліотеки, multi-stage розмір не врятує — потрібен інший базовий образ.

Окремий образ складання виправданий, коли складань багато і тулчейн у них спільний: тоді оновлення компілятора робиться в одному місці, а не в кожному файлі.

Порядок робіт: від аудиту шарів до передачі регламентів

Перш ніж змінювати складання, розбираємо, з чого складається поточний образ: які шари дають основний обсяг, що тягне зайві залежності, які файли потрапляють в артефакт через недогляд.

Аудит образу показує, де multi-stage dockerfile дасть реальний виграш, а де вузьке місце лежить у порядку інструкцій або в кешуванні.

  1. Аудит образу — знімаємо склад шарів, знаходимо найважчі, позначаємо пакети й файли, які не потрібні при запуску: кеші пакетних менеджерів, компілятори, тестові дані, службові каталоги.
  2. Схема етапів складання — розділяємо складання й запуск, вирішуємо, що переноситься у фінальний шар: зібраний артефакт, конфігурації, сертифікати, а що залишається тільки на етапі компіляції.
  3. Перенесення складання — переписуємо інструкції в новий файл, фіксуємо базові образи на актуальній гілці, задаємо непривілейованого користувача й робочий каталог.
  4. Перевірка відтворюваності — складаємо двічі на порожньому кеші та звіряємо результат: складання не повинно залежати від локальних файлів, порядку мережі й стану машини складальника.
  5. Звірка до і після — порівнюємо розмір образу, час складання, число шарів і час старту контейнера, переконуємося, що сервіс проходить свої перевірки працездатності.
  6. Передача регламенту складання — віддаємо файл, схему етапів, правила оновлення базових образів і порядок відкату на попередній тег.

Що замовник отримує: конфігурації, схеми шарів, регламенти

Результат роботи над multi-stage dockerfile — не один файл, а комплект артефактів, за якими команда складає й оновлює образи без нашої участі. Усе йде у ваш репозиторій, перевіряється на вашому стенді й супроводжується розбором: що змінено і чому саме так.

Комплект закриває цикл від контексту складання до відкату: фіксуємо, які шари кешуються і де образ починає зростати.

  • Dockerfile з явним розділенням етапів — складання залежностей, проміжна обробка, фінальний образ; з коментарями, що потрапляє в кожен шар.
  • Схема етапів складання: що копіюється між стадіями і які файли не доживають до фінального образу.
  • Файл винятків і правила контексту складання — зайве не відправляється в складання з самого початку.
  • Регламент оновлення базових образів: періодичність, відповідальний, перевірка сумісності й порядок відкату.
  • Інструкція для команди: як додати залежність, не роздуваючи фінальний шар, і як зібрати образ локально.
  • Порядок перевірок: порівняння розміру до і після, розбір складу шарів, перевірка запуску й працездатності.
  • Налаштований конвеєр складання з кешуванням шарів і публікацією тегів.
  • Запис розбору з командою: де вузькі місця, що залишено навмисно і який запас на зростання.

Опишіть задачу — повернемося з оцінкою та планом і покажемо, які артефакти потрібні вашому контуру, а що вже закрито вашими процесами.

Кейс: як переробка образу скоротила час викатки

До переробки сервіс складався одним Dockerfile: базовий образ з повним набором інструментів складання, зверху — вихідні коди, компілятори й кеш пакетних менеджерів.

Образ важив близько 1,2 ГБ, кожен вузол при викатці тягнув його повністю, а правка одного рядка в залежностях скидала кеш і запускала складання з нуля.

Що змінили

Розклали процес на стадії. Перша — складання: ставить залежності, компілює, проганяє тести. Друга — рантайм: тонка база, куди копіюється лише результат через COPY --from.

Додали .dockerignore, зафіксували тег базового образу й запуск від непривілейованого користувача — стадії складаються на актуальній гілці Docker, цього достатньо для передбачуваного кешу шарів.

Що стало

Складання скоротилося з одинадцяти хвилин до чотирьох: шар залежностей кешується окремо й не перезбирається при правці коду.

Розмір образу впав з 1,2 ГБ до 190 МБ — вузол забирає його за секунди, тому викатка перестала бути вікном очікування, а відкат на попередню версію став звичайною операцією.

Стабільність зросла не через швидкість, а через склад образу: у рантайм більше не потрапляють компілятори, заголовні файли й кеш пакетів, де й жили «плаваючі» залежності.

Частка викаток, які доводилося відкочувати вручну, за квартал упала з 18% до нуля, а розмір реєстру перестав зростати лінійно з кожним релізом.

Що робити, якщо базовий образ уже використовується в проді

Переводити сервіс на multi-stage dockerfile без зупинки роботи можна: нова схема складається паралельно з поточною, з того самого репозиторію і за тим самим тригером складання, тільки результат отримує окремий тег.

Прод продовжує жити на старому образі, поки новий проходить перевірку на стенді та порівняння метрик.

Перший крок у multi-stage dockerfile — винести встановлення залежностей і складання в окремий етап, залишивши у фінальному тільки артефакти й рантайм-бібліотеки.

Базовий образ на цьому кроці не змінюємо й фіксуємо за дайджестом, щоб результат збігався з продом: тоді відкат — це повернення попереднього тегу.

Поетапне впровадження йде через тестове середовище й канарковий деплой на частину інстансів. Порівнюємо не тільки розмір образу, а й час старту, споживання пам'яті та роботу з сертифікатами — основною гілкою новий тег стає тільки при збігу метрик.

Оновлення базового образу — окремий, пізніший крок: зміна рантайм-шару ламає зазвичай не код, а системні бібліотеки й кореневі сертифікати, тому образ закріплюють за дайджестом, а не за плаваючим тегом.

Головні ризики при переході — незбіг шляхів і прав доступу між етапами, втрата кешу шарів через перестановку інструкцій і складальне сміття, яке раніше потрапляло в образ.

Тому в конвеєр додають перевірку: запуск під непривілейованим користувачем і звірку списку файлів і портів з продом.

Технічні сумніви: кеш, залежності, базові образи

Перед стартом інженери задають одні й ті самі питання: що відбувається з кешем при зміні коду, як передати доступ до закритих реєстрів і як часто перезбирати образ, щоб базовий шар не застарівав. Нижче — як це влаштовано в наших складаннях.

Як порядок інструкцій впливає на кеш шарів?

Кеш перевикористовує шари до першої зміненої інструкції. Маніфести залежностей копіюємо раніше вихідного коду — правка одного модуля не інвалідує встановлення пакетів, і повторне складання триває хвилини, а не десятки хвилин. Розділення етапів складання й рантайму прибирає компілятори та проміжні файли з фінального образу.

Як передавати приватні залежності, не залишаючи їх в образі?

Облікові дані не можна передавати аргументами складання — вони фіксуються в історії шарів. Доступ до закритого реєстру даємо монтуванням секрету на час встановлення: тимчасовий конфіг живе всередині кроку і в образ не потрапляє. Перевіряємо це розбором готового образу — токенів і ключів у ньому не залишається.

Як часто оновлювати базовий образ і перезбирати?

Базовий образ фіксуємо за дайджестом: складання відтворюване, але уразливості не підтягуються самі. Оновлюємо планово — дивимося, що змінилося в базовому шарі, змінюємо фіксацію, проганяємо тести.

Перезбирання відбувається на кожен коміт і додатково за розкладом, тому оновлення безпеки доїжджають до проду протягом доби.

Якщо у вас закритий реєстр за периметром, кілька апаратних архітектур або жорсткі вимоги до відтворюваності — ці обмеження розбираємо на етапі проєктування, до того як складання піде в продакшн.

Обговоримо задачу: розберемо ваш образ і повернемося з оцінкою

Щоб повернутися з оцінкою термінів, а не із загальними формулюваннями, нам потрібен мінімум вихідних даних: поточний Dockerfile з усіма стадіями, файл залежностей, команду складання з конвеєра й лог останнього прогону з підсумковим розміром образу.

За ними відразу видно, які артефакти складання залишаються у фінальному шарі, скільки важать інструменти компіляції і де втрачається місце.

Далі розбір іде за кроками: на кожному зрозуміло, що ми віддаємо і що потрібно від вашого боку.

  • Збір контексту: Dockerfile, маніфести залежностей, змінні середовища, інструкція запуску.
  • Перевірка поточного складання: розподіл розміру за шарами й час складання на чистому кеші.
  • Обмеження: цільові платформи, вимоги до налагоджувальних інструментів у робочому образі, правила сканування уразливостей.
  • План робіт: які стадії виділяємо, що переносимо в стадію складання, що залишаємо у фінальній.
  • Що віддаємо: перероблений multi-stage dockerfile, схему стадій і пояснення щодо кожної зміни.
  • Оцінка термінів: розбивка за етапами, точки повернення на погодження й порядок перевірки результату.

Опишіть задачу й додайте те, що вже є під рукою, — повернемося з оцінкою та планом робіт. Якщо даних поки мало, підкажемо, що зібрати в першу чергу, щоб розбір ішов за фактами, а не за припущеннями.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    815
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139