Комментарии (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
- Меньше размер образа — удаляются инструменты сборки
- Быстрее загрузка — меньше данных для скачивания
- Безопаснее — в production нет компилятора и исходного кода
- Кэширование — пересчитываются только изменённые слои
- Управление зависимостями — jasne разделение сборки и runtime
Заключение
Multi-stage Builds — это стандартная практика в Docker, особенно для Java приложений. Использование этой техники позволяет:
- Значительно снизить размер Docker образов
- Ускорить CI/CD пайплайны
- Улучшить безопасность production образов
- Оптимизировать кэширование слоёв
Любой Java разработчик должен знать эту технику и применять её при создании Dockerfile для своих приложений.