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

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

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

Начать курс

Введение в Docker и Java

Современная разработка Java-приложений немыслима без контейнеризации. Docker — это платформа для упаковки, доставки и запуска приложений в изолированных средах (контейнерах). Для Java-разработчиков Docker решает вечную проблему: «На моей машине всё работает!». Контейнер гарантирует, что ваше приложение запустится одинаково на локальной машине, сервере разработки и в production.

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

Основы Docker для Java-приложений

Что такое Docker-образ и контейнер?

Docker-образ — это шаблон (read-only), содержащий всё необходимое для запуска приложения: JVM, библиотеки, код и настройки. Контейнер — запущенный экземпляр образа. Для Java это означает, что вам не нужно вручную устанавливать JDK на сервер — JVM уже будет внутри контейнера.

Создание простого Dockerfile для Java

Рассмотрим базовый пример. У нас есть простое Java-приложение (например, HelloWorld.java).

// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello from Docker and Java!");
    }
}

Создадим Dockerfile:

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

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

# Копируем скомпилированный jar или class файлы
COPY HelloWorld.class .

# Команда запуска
CMD ["java", "HelloWorld"]

Сборка и запуск:

# Сборка образа
docker build -t hello-java .

# Запуск контейнера
docker run hello-java

Вывод: Hello from Docker and Java!

Контейнеризация Spring Boot приложений

Для реальных проектов (например, на Spring Boot) процесс усложняется. Рассмотрим типичный микросервис.

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

Это лучшая практика для Java: сначала собираем приложение с полным JDK, затем копируем результат в легковесный образ JRE.

# Этап 1: Сборка
FROM maven:3.8.5-openjdk-11 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

# Этап 2: Запуск
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Объяснение:

  • build — использует Maven и полный JDK для компиляции.
  • Финальный образ — содержит только JRE и готовый jar-файл. Размер образа уменьшается в 2-3 раза.

Оптимизация кэширования слоёв

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

FROM maven:3.8.5-openjdk-11 AS build
WORKDIR /app
# Кэшируем зависимости
COPY pom.xml .
RUN mvn dependency:go-offline
# Теперь копируем код
COPY src ./src
RUN mvn clean package

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

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

Передача параметров в Java-приложение

Docker позволяет гибко настраивать приложение через переменные окружения. Для Spring Boot это особенно удобно.

docker run -e SPRING_PROFILES_ACTIVE=prod -e DB_URL=jdbc:postgresql://db:5432/mydb my-app

В Dockerfile можно задать значения по умолчанию:

ENV SPRING_PROFILES_ACTIVE=dev
ENV DB_URL=jdbc:h2:mem:testdb

Пример docker-compose для Java + БД

Для микросервисов удобно использовать docker-compose.yml:

version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_URL=jdbc:postgresql://db:5432/mydb
    depends_on:
      - db
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass

Запуск одной командой: docker-compose up

Лучшие практики и устранение проблем

1. Используйте .dockerignore

Аналог .gitignore. Исключите ненужные файлы из контекста сборки:

target/
*.log
.idea/
.git/
*.iml

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

Создайте непривилегированного пользователя внутри образа:

FROM openjdk:11-jre-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
COPY --from=build /app/target/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

3. Мониторинг памяти JVM в контейнере

Java в контейнере может неверно определять доступную память. Используйте флаги:

ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

Это заставит JVM использовать не более 75% памяти контейнера.

4. Логирование

Пишите логи в stdout/stderr — Docker собирает их автоматически:

// В logback.xml или application.properties
logging.file.path=/dev/null  # Отключаем файловое логирование
logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

Заключение

Docker стал стандартом де-факто для развертывания Java-приложений. Мы рассмотрели:

  • Создание

Блоги

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