Docker: як оптимізувати розмір контейнера з 50 ГБ до керованого рівня
Вступ
Контейнери давно стали стандартом де-факто для доставки застосунків у production. Проте з ростом складності систем часто виникає нетривіальна проблема - неконтрольоване збільшення розміру Docker-образів. Сценарій, коли образ досягає 30–50 ГБ, на практиці зустрічається значно частіше, ніж здається, особливо у проєктах із data science, CI/CD пайплайнами або legacy-залежностями.
Надмірний розмір контейнера створює низку критичних проблем: повільне розгортання, перевитрати дискового простору, збільшений час передачі через мережу, складність масштабування та деградація продуктивності CI/CD. У середовищах із високою частотою релізів це прямо впливає на бізнес-метрики.
У цій статті розглянемо системний підхід до оптимізації Docker-образів: від аналізу причин до впровадження практичних технік з прикладами. Матеріал орієнтований на інженерів рівня junior–senior і містить реальні кейси, інструменти та фрагменти коду.
Чому Docker-образи розростаються до десятків гігабайтів
Перш ніж оптимізувати, потрібно зрозуміти джерела проблеми. Найчастіші причини:
1. Невдалий вибір базового образу
Використання повноцінних дистрибутивів:
ubuntudebiancentos
замість мінімалістичних:
alpinedistrolessscratch
може додати кілька сотень мегабайт або навіть гігабайтів.
2. Зайві залежності та dev-інструменти
У контейнер часто потрапляють:
- компілятори (
gcc,make) - менеджери пакетів
- тестові бібліотеки
- кеші пакетів
3. Неконтрольовані layer-и
Кожна команда RUN, COPY, ADD створює новий layer. Якщо не очищати тимчасові файли, вони залишаються в історії образу.
4. Великі артефакти
Типові приклади:
- ML-моделі (кілька ГБ)
- лог-файли
- тимчасові дампи
- node_modules без оптимізації
5. Відсутність .dockerignore
Без цього файлу Docker копіює весь контекст, включно з:
.git- локальними build-артефактами
- кешами IDE
Аналіз поточного образу
Перед оптимізацією важливо провести аудит.
Перегляд розміру
docker images
Аналіз layer-ів
docker history <image_name>
Деталізація через dive
Інструмент:
dive <image_name>
Він показує:
- які файли займають місце
- які layer-и неефективні
- потенційні оптимізації
Базові стратегії оптимізації
Використання мінімалістичних базових образів
Було:
FROM ubuntu:22.04
Стало:
FROM alpine:3.19
Результат: зменшення базового шару з ~70 МБ до ~5 МБ.
Але важливо: Alpine використовує musl libc, що може викликати проблеми сумісності.
Multi-stage build
Один із найефективніших підходів.
Приклад для Go
# Stage 1: build
FROM golang:1.22 AS builder
WORKDIR /appCOPY . .
RUN go build -o app
# Stage 2: runtime
FROM alpine:3.19
WORKDIR /app
COPY --from=builder /app/app .
CMD ["./app"]
Результат:
- видаляються всі build-залежності
- фінальний образ містить лише binary
Очищення кешів
Поганий варіант:
RUN apt-get updateRUN apt-get install -y python3
Кращий варіант:
RUN apt-get update && \
apt-get install -y python3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Об'єднання команд RUN
Було:
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
Стало:
RUN apt-get update && \
apt-get install -y curl git && \
rm -rf /var/lib/apt/lists/*
Робота з .dockerignore
Файл .dockerignore критично важливий.
Приклад
.gitnode_modules*.logtmp/.cachedist/
Без нього контекст може збільшитися на десятки гігабайтів.
Оптимізація для Node.js
Проблема
node_modules часто займає 500 МБ–2 ГБ.
Рішення
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .CMD ["node", "app.js"]
Додатково
npm prune --production
Оптимізація Python-контейнерів
Типові проблеми
- кеш pip
- dev-залежності
- великі бібліотеки (numpy, pandas)
Приклад
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Оптимізація для Data Science (реальний кейс)
Початковий стан
- образ: 50 ГБ
- включає:
- Jupyter
- datasets
- checkpoints
- logs
Кроки оптимізації
- Винесення даних у volume або object storage (S3, MinIO)
- Видалення логів:
find /app -name "*.log" -delete
- Використання multi-stage build
- Очищення pip cache:
pip cache purge
- Перехід на slim-образ
Результат
- 50 ГБ → 3.2 ГБ
Використання distroless образів
Distroless містить тільки runtime.
FROM gcr.io/distroless/base
COPY app /app
CMD ["/app"]
Переваги:
- мінімальний розмір
- підвищена безпека
Видалення непотрібних файлів
Приклад
RUN rm -rf \
/usr/share/doc \
/usr/share/man \
/var/cache/*
Стиснення та оптимізація артефактів
Для Java
jlink --strip-debug --no-man-pages --no-header-files
Для Python
- використання
pyinstaller - створення одного binary
Контроль шарів (layer caching)
Порядок команд має значення
Погано:
COPY . .
RUN npm install
Краще:
COPY package.json .
RUN npm install
COPY . .
Це дозволяє кешувати залежності.
Використання BuildKit
DOCKER_BUILDKIT=1 docker build .
Переваги:
- кращий кеш
- паралельні збірки
- менші образи
Автоматичний аудит
Інструменти
divehadolintdocker-slim
Приклад docker-slim
docker-slim build myimage
Результат: автоматичне зменшення образу.
CI/CD оптимізація
Практики
- кешування layer-ів
- використання registry cache
- автоматичне очищення старих образів
Типові помилки
- Зберігання даних у контейнері
- Використання latest-тегів
- Ігнорування кешів
- Відсутність multi-stage build
- Копіювання всього проєкту без фільтрації
Комплексний приклад оптимізації
До
FROM ubuntu:22.04WORKDIR /app
COPY . .
RUN apt-get update
RUN apt-get install -y python3 pip
RUN pip install -r requirements.txt
CMD ["python3", "app.py"]
Після
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
Висновки
Оптимізація Docker-образів - це не одноразова дія, а постійний процес, який має бути інтегрований у життєвий цикл розробки. Контейнери розміром у десятки гігабайтів - це майже завжди результат накопичення технічного боргу, а не реальна необхідність.
Ключові принципи:
- мінімізація базового образу
- контроль залежностей
- використання multi-stage build
- очищення кешів і тимчасових файлів
- винесення даних за межі контейнера
У більшості практичних сценаріїв реалістично зменшити образ з 50 ГБ до 1 - 5 ГБ без втрати функціональності. Це дає відчутний виграш у швидкості деплою, стабільності та вартості інфраструктури.