Что такое multi-stage сборка в Docker и зачем она нужна?

Ответ

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

Основная цель — значительно уменьшить размер итогового Docker-образа, исключив из него всё ненужное для запуска приложения: инструменты сборки (компилятор Go, SDK), исходный код, временные файлы и зависимости.

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

# --- Этап 1: Сборка (Builder) ---
# Используем полный образ Go для компиляции
FROM golang:1.21-alpine as builder

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

# Копируем файлы зависимостей и загружаем их
COPY go.mod go.sum ./
RUN go mod download

# Копируем исходный код и собираем бинарный файл
COPY . .
# Флаги -ldflags="-s -w" убирают отладочную информацию, уменьшая размер
RUN go build -ldflags="-s -w" -o myapp

# --- Этап 2: Финальный образ (Final) ---
# Используем минималистичный образ, не содержащий ничего лишнего
FROM alpine:latest

WORKDIR /root/

# Копируем только скомпилированный бинарник из предыдущего этапа (builder)
COPY --from=builder /app/myapp .

# Указываем команду для запуска приложения
CMD ["./myapp"]

Ключевые преимущества:

  • Минимальный размер образа: Образ builder может весить >500 МБ (включая Go SDK), а финальный образ на основе alpine — всего 10-15 МБ.
  • Повышение безопасности: Меньше компонентов в итоговом образе означает меньшую поверхность для атак.
  • Чистота и порядок: В финальном образе содержатся только артефакты, необходимые для работы приложения.

Ответ 18+ 🔞

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

Смысл всей этой ёблы — на выходе получить образ, который весит не как чугунный мост, а как пустое ведро. Выкинуть нахуй всё лишнее: компиляторы, исходники, временные файлы — всё, что нужно только для постройки, но не для жизни.

Вот, смотри, как для Go-проги делают:

# --- Этап 1: Стройка (Builder) ---
# Берём здоровенный образ Go, чтоб всё скомпилить
FROM golang:1.21-alpine as builder

WORKDIR /app

# Тащим файлики с зависимостями и качаем их
COPY go.mod go.sum ./
RUN go mod download

# Тащим весь исходный код и лепим из него бинарник
COPY . .
# Эти флаги `-ldflags="-s -w"` вырезают отладочную хуйню, чтоб меньше весило
RUN go build -ldflags="-s -w" -o myapp

# --- Этап 2: Результат (Final) ---
# А теперь берём образ-голыш, alpine, в нём нихуя лишнего нет
FROM alpine:latest

WORKDIR /root/

# И вот тут магия: копируем ТОЛЬКО готовый бинарник из этапа `builder`
COPY --from=builder /app/myapp .

# И говорим, что запускать
CMD ["./myapp"]

И что мы, блядь, выигрываем?

  • Размер — овердохуища меньше: Образ builder может быть полгига, потому что там весь Go-мир. А финальный alpine — 10-15 мегов, кот наплакал.
  • Безопасность повыше: Чем меньше в образе всякого говна, тем меньше дырок для взлома.
  • Чистота, блядь, и порядок: В итоговом контейнере лежит только то, что приложению реально нужно для работы. Никакого мусора. Красота!