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 дасть реальний виграш, а де вузьке місце лежить у порядку інструкцій або в кешуванні.
- Аудит образу — знімаємо склад шарів, знаходимо найважчі, позначаємо пакети й файли, які не потрібні при запуску: кеші пакетних менеджерів, компілятори, тестові дані, службові каталоги.
- Схема етапів складання — розділяємо складання й запуск, вирішуємо, що переноситься у фінальний шар: зібраний артефакт, конфігурації, сертифікати, а що залишається тільки на етапі компіляції.
- Перенесення складання — переписуємо інструкції в новий файл, фіксуємо базові образи на актуальній гілці, задаємо непривілейованого користувача й робочий каталог.
- Перевірка відтворюваності — складаємо двічі на порожньому кеші та звіряємо результат: складання не повинно залежати від локальних файлів, порядку мережі й стану машини складальника.
- Звірка до і після — порівнюємо розмір образу, час складання, число шарів і час старту контейнера, переконуємося, що сервіс проходить свої перевірки працездатності.
- Передача регламенту складання — віддаємо файл, схему етапів, правила оновлення базових образів і порядок відкату на попередній тег.
Що замовник отримує: конфігурації, схеми шарів, регламенти
Результат роботи над 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, схему стадій і пояснення щодо кожної зміни.
- Оцінка термінів: розбивка за етапами, точки повернення на погодження й порядок перевірки результату.
Опишіть задачу й додайте те, що вже є під рукою, — повернемося з оцінкою та планом робіт. Якщо даних поки мало, підкажемо, що зібрати в першу чергу, щоб розбір ішов за фактами, а не за припущеннями.







