Какие методы уменьшения объема Docker-образа вы знаете?

Ответ

Сокращение размера Docker-образа ускоряет сборку, развертывание и повышает безопасность. Основные методы:

  1. Использование минимальных базовых образов:

    • Вместо ubuntu:latest или node:latest используйте alpine-варианты (node:alpine) или дистрибутивы типа scratch.
    • Почему: Alpine Linux основан на musl libc и BusyBox, что значительно уменьшает размер.
  2. Многоэтапная сборка (Multi-stage builds):

    • Отделите этап сборки приложения от этапа создания финального образа.
      
      # Этап сборки
      FROM golang:alpine AS builder
      WORKDIR /app
      COPY . .
      RUN go build -o myapp .

    Финальный этап

    FROM alpine:latest WORKDIR /root/ COPY --from=builder /app/myapp . CMD ["./myapp"]

    
    *   **Почему:** В финальный образ попадают только скомпилированный бинарник и его минимальные зависимости, без компилятора, исходного кода и промежуточных артефактов.
  3. Оптимизация Dockerfile-инструкций:

    • Объединяйте команды RUN в одну цепочку с && и очищайте кэш пакетного менеджера в том же слое.
      
      # Плохо: создает несколько слоев
      RUN apt-get update
      RUN apt-get install -y package
      RUN rm -rf /var/lib/apt/lists/*

    Хорошо: один слой, меньше места

    RUN apt-get update && apt-get install -y package && rm -rf /var/lib/apt/lists/*

    
    *   **Используйте `.dockerignore`**, чтобы не копировать в контекст сборки ненужные файлы (`.git`, `node_modules`, логи, временные файлы).
  4. Уменьшение количества слоев:

    • Копируйте все необходимые файлы одной инструкцией COPY, если это не мешает кэшированию.
    • Почему: Каждая инструкция в Dockerfile создает новый слой. Меньше слоев — меньше накладных расходов.
  5. Динамическое связывание библиотек:

    • При компиляции из исходного кода (C/C++, Go) используйте флаги для статической линковки только там, где это необходимо, чтобы не тащить все библиотеки в образ.

Практический результат: Применение этих методов может уменьшить образ с ~1.5 ГБ до ~150 МБ и менее.

Ответ 18+ 🔞

А, слушай, вот это реально больная тема, про которую все орут, а толком не объясняют, зачем это надо. Ну представь: у тебя образ, как твой старый комп, забитый всяким цифровым хламом — 1.5 гига. Каждый раз его на сервер таскать — ебать колотить, интернет плачет, место на диске кончается, да и злоумышленнику внутри такого сарая раздолье — спрятаться есть где. Наша задача — превратить этот сарай в стерильную, голую будку. Вот как это делается.

Первое и главное — не бери в базу жирную свинью.
Вместо того чтобы тянуть ubuntu:latest (который, блядь, как чемодан без ручки), смотри в сторону alpine. Это такой линукс-микроскоп, на котором можно запустить почти всё, но весит он, как перышко. Нужен Node.js? Бери node:alpine. Go? golang:alpine. Сразу минус гигабайт схуяля. Если совсем упороться — есть образ scratch, вообще пустой. Но это для сильных духом, когда твоё приложение — один статический бинарник.

Второе — волшебная многоэтапная сборка. Это просто гениальная вещь, ёпта.
Смысл в чём: ты на первом этаже (в одном образе) устраиваешь бардак — компилируешь, скачиваешь зависимости, пачкаешь всё. А потом из этого бардака забираешь только готовый, чистый артефакт (бинарник, jar-ник, что угодно) и кладёшь его в новый, девственно чистый образ. Всё остальное — компиляторы, исходники, временные файлы — остаются за бортом. Как будто ты построил машину в гараже, а потом выкатил из гаража только готовый автомобиль, а все верстаки, обрезки металла и пустые банки из-под пива — нахуй, в утиль.

Смотри, как это выглядит на практике:

# Этап-свинарник, где всё компилируется
FROM golang:alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .

# Финальный этап — стерильная палата
FROM alpine:latest
WORKDIR /root/
# Копируем ТОЛЬКО бинарник из предыдущего этапа. Ничего лишнего!
COPY --from=builder /app/myapp .
CMD ["./myapp"]

Видишь? В итоговом образе только алпайн и твоя программа. Ни Go, ни исходников, ни пакетов для сборки. Красота.

Третье — не будь распиздяем в Dockerfile.
Каждая команда RUN, COPY, ADD создаёт новый слой, который навсегда остаётся в истории. Если пишех хуйню вроде:

RUN apt-get update
RUN apt-get install -y какой-то-пакет
RUN rm -rf /var/lib/apt/lists/*

Ты получаешь три слоя, и удаление кэша в последнем — это пиздец какой обман, потому что предыдущие два слоя с данными уже намертво запечатаны. Они просто помечены как удалённые, но место-то едят! Правильно — делать всё в одной команде, чтобы мусор не успел осесть в отдельном слое:

RUN apt-get update && apt-get install -y какой-то-пакет 
    && rm -rf /var/lib/apt/lists/*

Один слой, в конце уборка. Идеально.

Четвёртое — файл .dockerignore, блядь, создай!
Это же просто пиздец, как многие его игнорят. Ты же не хочешь, чтобы в образ попали твои локальные node_modules, .git-папка весом в гигабайт, логи, кэши IDE или, не дай бог, файл .env с паролями? Так перечисли в .dockerignore всё, что не нужно, и Docker при копировании проигнорирует эту хуйню. Контекст сборки станет меньше, и сама сборка ускорится.

Итог: если не халявить, то образ в 1.5 ГБ легко превращается в 100-150 МБ. А то и меньше. Это быстрее качается, быстрее запускается, безопаснее и вообще — приятно глазу. Не будь свиньёй, оптимизируй.