Docker и Java: контейнеризация приложений — полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в Docker и Java: почему контейнеризация меняет разработку

Современная разработка программного обеспечения немыслима без контейнеризации. Docker стал стандартом де-факто для упаковки, доставки и запуска приложений. Для Java-разработчиков использование Docker решает ключевые проблемы: "это работает на моей машине" и сложности с настройкой окружения. В этой статье мы разберём, как контейнеризировать Java-приложения, от простого JAR-файла до сложного микросервиса на Spring Boot.

Docker позволяет изолировать приложение вместе с его зависимостями (JVM, библиотеки, системные утилиты) в лёгкий переносимый контейнер. Это особенно важно для Java, где версия JDK, настройки памяти и системные библиотеки могут сильно влиять на поведение программы.

Что вы узнаете из этой статьи

  • Создание Dockerfile для Java-приложений
  • Оптимизация размеров образов
  • Работа с многомодульными проектами Maven/Gradle
  • Лучшие практики безопасности и производительности

1. Основы: создание Dockerfile для Java-приложения

Начнём с простого примера. Предположим, у вас есть Java-приложение, собранное в JAR-файл. Вот минимальный Dockerfile, который упакует его в контейнер:

# Используем официальный образ OpenJDK
FROM openjdk:17-jdk-slim

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

# Копируем JAR-файл в контейнер
COPY target/myapp.jar app.jar

# Открываем порт, который слушает приложение
EXPOSE 8080

# Команда для запуска
ENTRYPOINT ["java", "-jar", "app.jar"]

Этот Dockerfile делает три простых вещи: берёт базовый образ с JDK 17, копирует скомпилированный JAR и запускает его. Однако в реальных проектах такой подход неэффективен — каждый раз при изменении кода Docker пересобирает весь образ, включая копирование JAR.

Многоступенчатая сборка (Multi-stage build)

Для разделения этапов сборки и выполнения используйте многоступенчатые Dockerfile. Это уменьшает финальный образ, исключая инструменты сборки (Maven, Gradle, JDK для компиляции).

# Этап сборки
FROM maven:3.9.6-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

# Финальный этап
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Первый этап использует полный Maven-образ для компиляции, второй — минимальный JRE-образ для запуска. Финальный образ весит ~200 МБ вместо 700+ МБ.

2. Оптимизация образов: уменьшаем размер и ускоряем сборку

Большие образы замедляют развёртывание и увеличивают время загрузки. Вот несколько техник для Java:

Используйте JLink для создания минимальной JRE

Начиная с Java 9, вы можете создать кастомную JRE, содержащую только необходимые модули. Это радикально уменьшает размер образа.

FROM openjdk:17-jdk-slim AS jre-builder
RUN jlink \
    --add-modules java.base,java.logging,java.sql \
    --strip-debug \
    --no-man-pages \
    --no-header-files \
    --compress=2 \
    --output /jre

FROM debian:bookworm-slim
COPY --from=jre-builder /jre /jre
WORKDIR /app
COPY target/myapp.jar app.jar
ENV PATH="/jre/bin:${PATH}"
ENTRYPOINT ["java", "-jar", "app.jar"]

Такой образ может весить всего 40-60 МБ вместо 200+ МБ.

Кэширование слоёв Docker

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

FROM maven:3.9.6-eclipse-temurin-17 AS builder
WORKDIR /build
# Сначала копируем только pom.xml и скачиваем зависимости
COPY pom.xml .
RUN mvn dependency:go-offline
# Затем копируем исходники и собираем
COPY src ./src
RUN mvn clean package

Теперь, если вы измените только код, Docker пересоберёт только последние два слоя, а не все зависимости.

3. Docker Compose: запуск Java-приложения с зависимостями

Реальные приложения редко работают в одиночку — им нужна база данных, кеш или очередь сообщений. Docker Compose позволяет описать всю инфраструктуру в одном YAML-файле.

version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mydb
      - SPRING_DATASOURCE_USERNAME=user
      - SPRING_DATASOURCE_PASSWORD=pass
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Этот Compose-файл поднимает ваше Java-приложение и PostgreSQL. Сервис app использует depends_on, чтобы гарантировать, что база данных запустится первой (но не дожидается её готовности — для этого нужны healthcheck).

Работа с переменными окружения

Java-приложения, особенно на Spring Boot, активно используют переменные окружения для конфигурации. В Dockerfile или Compose вы можете задать их:

# В Dockerfile
ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Или в Docker Compose:

environment:
  - JAVA_OPTS=-Xmx512m -Xms256m
  - SPRING_PROFILES_ACTIVE=production

4. Безопасность и лучшие практики для Java-контейнеров

Контейнеризация не отменяет необходимость безопасности. Вот ключевые моменты для Java:

Не запускайте контейнер от root

По умолчанию

Блоги

Рекомендаций по книгам