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

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

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

Начать курс

Почему контейнеризация Kotlin — это маст-хев для современного разработчика?

Представьте ситуацию: вы написали крутое Kotlin-приложение на своей MacBook. Всё работает идеально. Вы скидываете код коллеге на Windows — база данных не подключается. Ещё одному на Linux — версия JDK не та. Знакомо? Docker решает эту проблему раз и навсегда.

Контейнеризация Kotlin приложений — это не просто модный тренд. Это способ гарантировать, что ваше приложение будет работать одинаково на любой машине: от ноутбука джуниора до production-сервера в AWS. В этой статье мы разберём, как правильно упаковать Kotlin-приложение в Docker-образ, оптимизировать его размер и настроить для продакшена.

Что вы узнаете:

  • Как написать Dockerfile для Kotlin-приложения с Gradle
  • Как уменьшить размер образа в 3-5 раз
  • Как использовать Docker Compose для связки Kotlin + база данных
  • Best practices для production-контейнеризации

Базовый Dockerfile для Kotlin-приложения

Начнём с простого. Допустим, у вас есть стандартное Kotlin-приложение, которое собирается с помощью Gradle. Первое, что приходит в голову — взять образ JDK, скопировать исходники и запустить сборку. Давайте так и сделаем, но сразу с учётом best practices.

Монолитная сборка (не делайте так в продакшене)

# Dockerfile
FROM gradle:7.6-jdk17 AS build
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts ./
COPY src ./src
RUN gradle build --no-daemon -x test

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=build /app/build/libs/app.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Этот подход рабочий, но у него есть три серьёзных недостатка:

  • Размер: образ весит около 400-500 МБ из-за полного JDK
  • Кэширование: при любом изменении исходников Gradle будет скачивать зависимости заново
  • Безопасность: в образе остаются инструменты сборки, которые не нужны в рантайме

Оптимизированная сборка: многоэтапность и кэширование

Настоящая магия Docker — это multi-stage builds. Мы разделим процесс на три этапа:

  • Загрузка зависимостей (кэшируется)
  • Сборка приложения
  • Финальный минимальный образ

Правильный Dockerfile для Kotlin + Gradle

# Stage 1: Загрузка зависимостей
FROM gradle:7.6-jdk17 AS dependencies
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts ./
RUN gradle dependencies --no-daemon

# Stage 2: Сборка
FROM dependencies AS build
COPY src ./src
RUN gradle build --no-daemon -x test

# Stage 3: Финальный образ
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/build/libs/app.jar app.jar
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Что изменилось?

  • Alpine JRE вместо полного JDK — образ уменьшается до ~120 МБ
  • Кэширование зависимостей — если не менялся build.gradle.kts, Docker использует кэш
  • Безопасность — приложение запускается от непривилегированного пользователя

Сборка с Gradle Wrapper (рекомендуемый способ)

Если вы используете Gradle Wrapper (а вы должны!), Dockerfile становится ещё элегантнее. Wrapper гарантирует, что версия Gradle будет одинаковой у всех разработчиков и в CI/CD.

FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY gradlew gradlew.bat ./
COPY gradle ./gradle
RUN ./gradlew --version

COPY build.gradle.kts settings.gradle.kts ./
RUN ./gradlew dependencies --no-daemon

COPY src ./src
RUN ./gradlew build --no-daemon -x test

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/app.jar app.jar
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Обратите внимание: мы сначала копируем только Wrapper и конфигурационные файлы, чтобы максимально эффективно использовать кэш Docker.

Контейнеризация Kotlin-приложения с базой данных: Docker Compose

В реальных проектах Kotlin-приложение редко живёт само по себе. Чаще всего оно взаимодействует с PostgreSQL, Redis или Kafka. Здесь на помощь приходит Docker Compose.

Пример docker-compose.yml для Kotlin + PostgreSQL

version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DB_URL=jdbc:postgresql://db:5432/mydb
      - DB_USER=user
      - DB_PASSWORD=secret
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=secret
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

Ключевые моменты:

  • Healthcheck — приложение стартует только после того, как PostgreSQL полностью готов
  • Volumes — данные базы не теряются при перезапуске контейнера
  • Переменные окружения — конфигурация вынесена из кода

Оптимизация образа для продакшена

Когда дело доходит до продакшена, каждый мегабайт имеет значение. Вот несколько техник, которые помогут сделать образ ещё легче и безопаснее.

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

jlink позволяет собрать кастомную JRE, включив только те модули, которые реально нужны вашему приложению. Это может уменьшить размер образа до 40-50 МБ.

FROM eclipse-temurin:17-jdk-alpine AS jre-builder
RUN jlink \
    --add-modules java.base,java.sql,java.naming,java.management \
    --strip-debug \
    --no-man-pages \
    --no-header-files \
    --compress=2 \
    --output /jre

FROM alpine:3.18
ENV JAVA_HOME=/jre
ENV PATH="$JAVA_HOME/bin:$PATH"
COPY --from=jre-builder /jre                

Блоги

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