Многоступенчатые Dockerfile оптимизация размера — тема, которая волнует разработчиков и инженеров по развёртыванию не случайно. Маленький образ загружается быстрее, требует меньше диска и упрощает безопасность, но путь к компактности часто тернист: лишние зависимости, неправильно сконструированные слои и неверные базовые образы быстро раздувают контейнер. В этой статье я разберу принципы, приёмы и реальные приёмники, которые помогут системно уменьшать размер образов без риска сломать приложение.
Почему размер образа важен
Большие образы влияют на все этапы жизненного цикла приложения: от локальной разработки до CI/CD и продакшена. Каждый мегабайт добавляет время при скачивании, увеличивает расходы на хранение и повышает вероятность уязвимостей за счёт лишнего ПО внутри контейнера.
Кроме практических потерь, крупные образы усложняют отладку. Когда в слое скрывается много временных или строительных артефактов, понять причину проблемы становится труднее. Контроль размера — это одновременно про предсказуемость и про безопасность.
Как работают многоступенчатые сборки
Идея многоступенчатой сборки проста: разделить процесс создания артефактов и конечного образа на отдельные этапы. На одном этапе вы компилируете, устанавливаете зависимости и запускаете тесты; на финальном — берёте только то, что нужно для запуска: бинарники, артефакты сборки и минимальный набор библиотек.
Технически это реализуется директивой COPY —from=… в Dockerfile. Благодаря этому в итоговый образ не попадают временные файлы и инструменты сборки. Такой подход позволяет использовать тяжёлые базовые образы для сборки и лёгкие для рантайма.
Ключевые стратегии уменьшения размера образа
Подходов много, но они не равнозначны для всех языков и стеков. Я расскажу о проверенных приёмах, которые применимы к большинству проектов, и отмечу, где стоит проявить осторожность.
Каждый метод экономит место по‑разному: кто-то убирает кеши и мета‑данные, кто‑то меняет базовый образ, а кто‑то оптимизирует артефакты сборки. Лучший результат дают сочетания приёмов, а не один волшебный флаг.
Используйте минимальный базовый образ для рантайма
Вместо полного образа с инструментами сборки берите облегченную версию или специальные рантайм‑образы. Для Go и статически собранных программ часто применяют scratch или distroless; для Node и Python — slim или alpine, в зависимости от зависимости от glibc.
При выборе учитывайте совместимость. Alpine экономит место, но пакетная экосистема и совместимость с glibc иногда создают проблемы. Лучше протестировать заранее, чем переносить баги в продакшн.
Отделяйте этапы сборки от этапа исполнения
Многоступенчатые Dockerfile позволяют держать инструменты сборки в отдельном слое, который не попадает в итоговый образ. На этапе сборки устанавливается компилятор, пакеты для тестов и временные зависимости, а на финальном — только runtime‑зависимости и артефакты.
Типичный паттерн: builder stage, где выполняется сборка, и runtime stage, куда копируется только результат. Такой паттерн уменьшает поверхность атаки и делает образ чище.
Чистите временные файлы и кеши в одном RUN
Каждая команда RUN создаёт слой, поэтому установка пакетов и их очистка должны выполняться в одной команде. Для apt это значит: apt-get update && apt-get install … && rm -rf /var/lib/apt/lists/* в рамках одного RUN. Так кеши не попадут в итоговый слои.
Если используете BuildKit, можно применять специальные примонтированные кеши, которые не коммитятся в образ. Это упрощает управление зависимостями и оставляет итоговый образ свободным от временных данных.
Игнорируйте лишние файлы через .dockerignore
Контекст сборки передаётся демону Docker целиком, если не использовать .dockerignore. Включите в него локальные зависимости, папки с IDE, логи и большие медиафайлы — всё, что не требуется для сборки. Это сокращает время передачи контекста и предотвращает случайную упаковку больших файлов в образ.
Создавая .dockerignore, думайте не только о размере, но и о безопасности: исключайте файлы с секретами и конфигами, которые не должны попадать в контейнер.
Минимизируйте набор установленных пакетов
Устанавливайте только то, что реально нужно для выполнения сервиса. Для apt и apk есть опции, уменьшающие установку рекомендованных пакетов: —no-install-recommends для apt и —no-cache для apk. Это простой способ избежать дополнительных библиотек.
Иногда имеет смысл собирать бинарники отдельно и копировать их в рантайм‑образ вместо установки через пакетный менеджер. Такой подход особенно эффективен для языков со статической компоновкой.
Удаляйте отладочные символы и ненужные файлы
Стриппинг бинарников (strip) удаляет символы отладки и может заметно уменьшить размер. Для языков с дополнительными артефактами — карта исходников, тестовые данные — важно исключать их из финального образа. Подумайте о запуске этапа оптимизации перед копированием в runtime.
Если приложение генерирует временные файлы при установке зависимостей, удаляйте их до копирования в следующий этап. Это сохраняет образ компактным и прозрачным.
Смотрите в сторону distroless и scratch
Образы distroless содержат исключительно минимальный рантайм без шелла и пакетных менеджеров. Они значительно уменьшают размер и снижают вектор атак. Scratch — ещё более экстремальная опция: пустой образ, куда копируется только статически собранный бинарный файл.
Недостаток таких подходов в отладке: внутри контейнера нет утилит для диагностики. Поэтому в процессе разработки стоит иметь альтернативный образ с инструментами, а в продакшн — легкий рантайм.
Инструменты анализа и сборки
Чтобы понять, что именно занимает место в образе, нужны инструменты: docker history показывает слои и их размеры, docker image inspect возвращает метаданные, а утилиты вроде dive дают визуальный разбор содержимого слоёв и файловую структуру. Эти инструменты помогают выявить «тяжёлые» файлы и нерациональные слои.
BuildKit стоит включить в сборки: он поддерживает оптимизированные примонтированные кеши, секреты и уменьшает количество промежуточных слоёв. При правильном использовании BuildKit делает образы компактнее и сборки стабильнее.
Краткий практический пример
Ниже — типичный паттерн для Go-приложения: на первом этапе компиляция в standalone бинарник, на финальном — копирование в лёгкий рантайм. Такой подход часто даёт существенную экономию по сравнению с переносом сборочных инструментов в финальный образ.
FROM golang:1.20 AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o /myapp ./cmd FROM scratch COPY --from=builder /myapp /myapp ENTRYPOINT ["/myapp"]
Здесь отсутствуют инструменты и кеши в финальном образе: только исполняемый файл. Подобный паттерн легко адаптируется под другие языки, где результат сборки — набор статичных артефактов.
Контроль и чеклист перед релизом
Перед тем как пушить образ в регистр, пройдите простую последовательность: проверьте .dockerignore, проанализируйте docker history, уберите ненужные пакеты и кеши, убедитесь, что в финальном образе нет инструментов сборки. Этот чеклист помогает избежать типичных ошибок.
- Проверьте .dockerignore на предмет больших файлов и секретов.
- Используйте многоступенчатую сборку: разделите build и runtime.
- Очищайте кеши и комбинируйте команды RUN, где это уместно.
- Выберите минимальный базовый образ, совместимый с вашим стеком.
- Проанализируйте результаты с помощью dive или docker history.
Эти простые шаги часто дают быстрее и надёжнее эффект, чем попытки оптимизировать отдельные команды в слепую.
Личный опыт и подводные камни
В одном проекте мне пришлось сочетать несколько приёмов: убрать dev‑зависимости, перенести сборку в отдельный этап и перейти на distroless для рантайма. Результат не появился мгновенно — пришлось решать несовместимости библиотек и настраивать CI — но итоговая картина стала прозрачнее, а образы — легче.
Главная ловушка в оптимизации — желание «уже сэкономить» путём агрессивных правок без тестов. Удаление пакета, который кажется ненужным, может привести к неожиданным ошибкам в рантайме. Оптимизируйте постепенно и запускайте проверку работоспособности после каждого шага.
Последние рекомендации перед применением
Оптимизация размера образа — не цель сама по себе. Это инструмент улучшения доставки, безопасности и эксплуатации. Начинайте с анализа: поймите, что занимает место, затем применяйте техники в порядке отдачи к усилиям.
Всегда держите баланс между компактностью и удобством отладки. Для разработки можно использовать более «толстые» образы, а в продакшн выкладывать максимально чистые рантаймы с минимальным набором компонентов.

