Как использовать multi-stage Dockerfile?

Ответ

Multi-stage Dockerfile — это мощный инструмент для создания оптимизированных и безопасных Docker-образов. Он позволяет разделить процесс сборки приложения на несколько этапов, используя промежуточные образы для выполнения задач (например, компиляции, установки зависимостей), а затем копируя только необходимые артефакты в финальный, минимальный образ.

Почему это важно?

  • Уменьшение размера образа: Финальный образ не содержит инструментов сборки, временных файлов и ненужных зависимостей.
  • Улучшение безопасности: Меньше компонентов в финальном образе означает меньшую поверхность атаки.
  • Ускорение сборки: Кэширование слоев на разных этапах может ускорить повторные сборки.

Пример для Python-приложения:

Предположим, у нас есть requirements.txt и app.py.

# --- Этап 1: Сборка и установка зависимостей (builder) ---
# Используем более полный образ для установки зависимостей
FROM python:3.9-slim as builder

# Устанавливаем рабочую директорию
WORKDIR /app

# Копируем файл зависимостей
COPY requirements.txt .

# Устанавливаем зависимости в пользовательскую директорию
# Это позволяет легко скопировать их на следующем этапе
RUN pip install --user -r requirements.txt

# --- Этап 2: Финальный образ (production) ---
# Используем минимальный образ для продакшена
FROM python:3.9-slim

# Устанавливаем рабочую директорию
WORKDIR /app

# Копируем установленные пакеты из этапа 'builder'
# /root/.local/lib/python3.9/site-packages - стандартный путь для --user установки в slim-образах
COPY --from=builder /root/.local /root/.local

# Копируем исходный код приложения
COPY . .

# Добавляем пользовательскую директорию с пакетами в PATH
# Это необходимо, чтобы Python мог найти установленные зависимости
ENV PATH="/root/.local/bin:$PATH"

# Команда для запуска приложения
CMD ["python", "app.py"]

Пояснения к примеру:

  1. FROM python:3.9-slim as builder: Первый этап, названный builder, использует python:3.9-slim для установки зависимостей.
  2. RUN pip install --user -r requirements.txt: Зависимости устанавливаются в пользовательскую директорию (/root/.local), чтобы их было легко изолировать и скопировать.
  3. FROM python:3.9-slim: Второй этап начинается с чистого python:3.9-slim образа, который будет финальным.
  4. COPY --from=builder /root/.local /root/.local: Копируем только установленные зависимости из builder этапа в финальный образ.
  5. ENV PATH="/root/.local/bin:$PATH": Обновляем переменную окружения PATH, чтобы исполняемые файлы из установленных пакетов были доступны.

Этот подход значительно уменьшает размер итогового образа, так как он не содержит pip и других инструментов, использованных только на этапе сборки.

Ответ 18+ 🔞

А, ну это же классика, блядь! Multi-stage Dockerfile — это как если бы ты на стройке сначала привёз здоровенную бетономешалку, кучу инструментов и бригаду гопников, чтобы дом построить, а потом, когда всё готово, оставил только сам дом, а всю эту ебанину — нахуй, обратно увез. И живёшь теперь в чистом, аккуратном домике, а не в сарае, заваленном грязными лопатами и пустыми бутылками из-под водки.

Зачем это, спрашивается, нужно?

  • Образ меньше, блядь. Финальная картинка — это как чемодан в аэропорту: тащишь только трусы, зубную щётку и ноутбук, а не всю мастерскую с паяльником и тисками.
  • Безопаснее, ёпта. Чем меньше в образе всякого говна — компиляторов, временных файлов, лишних либ — тем меньше дырок, куда злоумышленник может сунуть свой любопытный нос. Поверхность атаки, блядь, меньше!
  • Собирается быстрее. Этапы кэшируются по-отдельности. Изменил только исходник — второй этап пересоберётся, а первый, где зависимости ставятся, останется как есть, из кэша.

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

Допустим, есть у нас requirements.txt и app.py.

# --- Этап 1: Сборщик (builder) — тут мы наводим марафет ---
# Берём относительно толстый образ, чтобы всё поставить
FROM python:3.9-slim as builder

WORKDIR /app

# Тащим файлик с зависимостями
COPY requirements.txt .

# Ставим зависимости, но НЕ в системные дебри, а в свою личную папку пользователя
# Чтобы потом, как чемодан, легко перехватить
RUN pip install --user -r requirements.txt

# --- Этап 2: Продакшн — тут мы живём ---
# Берём тот же минимальный образ, но ЧИСТЫЙ, блядь
FROM python:3.9-slim

WORKDIR /app

# А вот и магия! Крадём из этапа 'builder' только нужное — папку с установленными пакетами
# /root/.local — это как раз то самое место, куда `--user` ставит
COPY --from=builder /root/.local /root/.local

# Копируем наш гениальный код
COPY . .

# Теперь важно сказать системе: "Слушай, дружок, ищи исполняемые файлы ещё и вот в этой нашей личной папке"
# А то она нихуя не найдёт и будет орать, что модулей нет
ENV PATH="/root/.local/bin:$PATH"

# И поехали!
CMD ["python", "app.py"]

Что тут произошло, ёпта:

  1. FROM ... as builder: Первый этап, назвали его builder. Тут мы, как свиньи, навалили кучу всего: pip, зависимости, временные файлы сборки.
  2. RUN pip install --user ...: Хитрость в --user. Всё ставится не в системные недра, а в отдельную папочку (/root/.local). Как будто сложил инструменты не в общий гараж, а в свой личный ящик.
  3. FROM python:3.9-slim: Второй этап. Берём новый, девственно чистый образ. Никакого pip, никакого мусора.
  4. COPY --from=builder /root/.local ...: Волшебная команда! Из этапа builder забираем ТОЛЬКО наш "личный ящик" с установленными пакетами. Всё остальное говно из первого этапа остаётся там и в финальный образ не попадает.
  5. ENV PATH="...": Направляем систему, чтобы она искала команды и в нашей личной папке. Без этого шага питон будет орать, что ничего не знает про твои установленные библиотеки.

Итог: образ легковесный, безопасный и содержит ровно то, что нужно для работы, а не для стройки. Красота, блядь!