← Назад к вопросам

Что такое Multi-stage Builds?

2.0 Middle🔥 121 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое Multi-stage Builds

Multi-stage Builds (многоэтапные сборки) — это техника в Docker, которая позволяет использовать несколько FROM инструкций в одном Dockerfile для создания более компактных образов. Первые этапы могут содержать инструменты сборки (compiler, dependency manager), а финальный образ содержит только необходимое для работы приложения.

Проблема, которую решают Multi-stage Builds

# Старый способ (неправильно) — один большой образ
FROM openjdk:11

# Устанавливаем Maven
RUN apt-get update && apt-get install -y maven git

# Копируем исходный код
COPY . /app
WORKDIR /app

# Собираем приложение
RUN mvn clean package

# Запускаем приложение
CMD ["java", "-jar", "target/app.jar"]

# Проблемы:
# - Образ содержит Maven, git, исходный код — всё ненужное в production
# - Размер образа: 1-2 GB (огромный!)
# - Содержит инструменты, которые могут быть используемы для атак
# - Медленная загрузка и развёртывание

Решение: Multi-stage Build

# STAGE 1: Сборка (Build Stage)
FROM openjdk:11 as builder

# Устанавливаем инструменты
RUN apt-get update && apt-get install -y maven

# Копируем код
COPY . /app
WORKDIR /app

# Собираем приложение
RUN mvn clean package -DskipTests

# STAGE 2: Production (Runtime Stage)
FROM openjdk:11-jre-slim

# Копируем ТОЛЬКО собранный JAR из Stage 1
COPY --from=builder /app/target/app.jar /app/app.jar

WORKDIR /app

# Запускаем приложение
CMD ["java", "-jar", "app.jar"]

# Результат:
# - Stage 1 используется только для сборки, потом удаляется
# - Stage 2 содержит только JRE (lightweight) и JAR файл
# - Размер образа: 150-300 MB (в 5-10 раз меньше!)
# - Нет Maven, исходного кода, git — безопаснее

Как это работает

┌─────────────────────────────────────────┐
│ STAGE 1: builder (Build)                │
│ ┌──────────────────────────────────────┐ │
│ │ FROM openjdk:11                      │ │
│ │ + Maven                              │ │
│ │ + Компилируем Java код               │ │
│ │ = app.jar в /app/target/             │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────┘
           ↓ (копируем JAR)
┌─────────────────────────────────────────┐
│ STAGE 2: final (Runtime)                │
│ ┌──────────────────────────────────────┐ │
│ │ FROM openjdk:11-jre-slim             │ │
│ │ + app.jar                            │ │
│ │ = готовый образ для production       │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────┘

Этап 1 (400 MB) -> удаляется
Этап 2 (150 MB) -> итоговый образ

Практический пример: Spring Boot приложение

# Stage 1: Build
FROM maven:3.8-openjdk-11 as builder

WORKDIR /workspace

# Копируем pom.xml и скачиваем зависимости
COPY pom.xml .
RUN mvn dependency:go-offline

# Копируем исходный код и собираем
COPY src src
RUN mvn clean package -DskipTests

# Stage 2: Runtime
FROM openjdk:11-jre-slim

WORKDIR /app

# Копируем только JAR из Stage 1
COPY --from=builder /workspace/target/spring-app.jar app.jar

# Expose порт
EXPOSE 8080

# Запускаем приложение
ENTRYPOINT ["java", "-jar", "app.jar"]

Оптимизация многоэтапной сборки

Версия 1: Базовая (медленная)

FROM maven:3.8-openjdk-11 as builder

WORKDIR /workspace

# Проблема: при каждом изменении кода загружаются ВСЕ зависимости
COPY . .
RUN mvn clean package

FROM openjdk:11-jre-slim
COPY --from=builder /workspace/target/app.jar app.jar
CMD ["java", "-jar", "app.jar"]

Версия 2: Оптимизированная (быстрая) — кэширование зависимостей

FROM maven:3.8-openjdk-11 as builder

WORKDIR /workspace

# Копируем только POM (редко меняется)
COPY pom.xml .

# Скачиваем зависимости (кэшируется между сборками)
RUN mvn dependency:go-offline

# Копируем исходный код (часто меняется)
COPY src src

# Собираем
RUN mvn clean package -DskipTests

FROM openjdk:11-jre-slim
COPY --from=builder /workspace/target/app.jar app.jar
CMD ["java", "-jar", "app.jar"]

# Результат:
# - Если pom.xml не изменился, зависимости берутся из кэша (30 сек вместо 3 мин)
# - Если только код изменился, повторно скачиваются только зависимости (быстро)

Многоэтапные сборки с Gradle

# Stage 1: Build
FROM gradle:7-jdk11 as builder

WORKDIR /gradle

# Копируем Gradle конфиги (кэшируется)
COPY build.gradle settings.gradle ./
RUN gradle build --no-daemon -x test 2>&1 | head -1

# Копируем исходный код
COPY src src
RUN gradle build --no-daemon -x test

# Stage 2: Runtime
FROM openjdk:11-jre-slim
COPY --from=builder /gradle/build/libs/app.jar app.jar
CMD ["java", "-jar", "app.jar"]

Практический пример с dotnet (для понимания)

# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:6.0 as builder

WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app/publish

# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/runtime:6.0
WORKDIR /app
COPY --from=builder /app/publish .
CMD ["dotnet", "app.dll"]

Сравнение размеров образов

# Сборка без многоэтапного подхода
$ docker build -f Dockerfile.old -t java-app:old .
$ docker images java-app:old
# Size: 1.2 GB

# Сборка с многоэтапным подходом
$ docker build -f Dockerfile.multi -t java-app:multi .
$ docker images java-app:multi
# Size: 250 MB

# Экономия: 1.2 GB -> 250 MB (в 5 раз меньше!)

Продвинутые техники

Named stages (именованные этапы)

# Stage 1: Dependency resolver
FROM maven:3.8 as deps
WORKDIR /workspace
COPY pom.xml .
RUN mvn dependency:go-offline

# Stage 2: Builder
FROM maven:3.8 as builder
WORKDIR /workspace
COPY --from=deps /root/.m2 /root/.m2
COPY . .
RUN mvn clean package

# Stage 3: Final
FROM openjdk:11-jre-slim
COPY --from=builder /workspace/target/app.jar app.jar
CMD ["java", "-jar", "app.jar"]

Условная сборка

FROM openjdk:11 as builder
ARG BUILD_TYPE=release

COPY . /app
WORKDIR /app

RUN if [ "$BUILD_TYPE" = "debug" ]; then \
      mvn clean package; \
    else \
      mvn clean package -DskipTests; \
    fi

FROM openjdk:11-jre-slim
COPY --from=builder /app/target/app.jar app.jar
CMD ["java", "-jar", "app.jar"]

# Сборка
# docker build --build-arg BUILD_TYPE=debug -t java-app:debug .
# docker build --build-arg BUILD_TYPE=release -t java-app:release .

Java разработчик и Docker

// Spring Boot приложение
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// pom.xml
<properties>
    <java.version>11</java.version>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>

// Dockerfile для этого приложения
FROM maven:3.8-openjdk-11 as builder
WORKDIR /workspace
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src src
RUN mvn clean package -DskipTests

FROM openjdk:11-jre-slim
COPY --from=builder /workspace/target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Преимущества Multi-stage Builds

  1. Меньше размер образа — удаляются инструменты сборки
  2. Быстрее загрузка — меньше данных для скачивания
  3. Безопаснее — в production нет компилятора и исходного кода
  4. Кэширование — пересчитываются только изменённые слои
  5. Управление зависимостями — jasne разделение сборки и runtime

Заключение

Multi-stage Builds — это стандартная практика в Docker, особенно для Java приложений. Использование этой техники позволяет:

  • Значительно снизить размер Docker образов
  • Ускорить CI/CD пайплайны
  • Улучшить безопасность production образов
  • Оптимизировать кэширование слоёв

Любой Java разработчик должен знать эту технику и применять её при создании Dockerfile для своих приложений.

Что такое Multi-stage Builds? | PrepBro