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

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

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

Начать курс

Введение: почему Docker и Kotlin — идеальная пара

Современная разработка немыслима без контейнеризации. Docker стал стандартом де-факто для упаковки, доставки и запуска приложений. Kotlin, в свою очередь, стремительно набирает популярность благодаря лаконичному синтаксису, полной совместимости с Java и поддержке мультиплатформенности. Сочетание Docker и Kotlin позволяет создавать легковесные, масштабируемые и предсказуемые окружения — от простого серверного приложения на JVM до нативного бинарника для IoT.

В этой статье мы разберем, как правильно контейнеризировать Kotlin-проекты, оптимизировать размер образов и избежать типичных ошибок. Все примеры кода вы можете адаптировать под свой проект.

1. Базовая контейнеризация Kotlin/JVM приложения

Начнем с классического сценария: Kotlin-приложение, работающее на JVM. Для сборки будем использовать Gradle — стандартный инструмент для Kotlin-проектов.

1.1. Структура проекта

Предположим, у нас есть простой HTTP-сервер на Ktor. Структура проекта:

my-kotlin-app/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
    └── main/
        └── kotlin/
            └── com/
                └── example/
                    └── App.kt

1.2. Dockerfile для мультистадийной сборки

Ключевая практика — многостадийная сборка (multi-stage build). Первый этап собирает fat JAR с помощью Gradle, второй — использует минимальный образ JRE для запуска.

# Стадия 1: сборка
FROM gradle:8.5-jdk21 AS build
WORKDIR /app
COPY --chown=gradle:gradle . .
RUN gradle build --no-daemon -x test

# Стадия 2: запуск
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

1.3. Оптимизация: кэширование зависимостей

Чтобы не пересобирать зависимости при каждом изменении кода, разделите COPY на две части:

FROM gradle:8.5-jdk21 AS build
WORKDIR /app
# Сначала копируем только файлы сборки
COPY build.gradle.kts settings.gradle.kts ./
COPY gradle/ gradle/
RUN gradle dependencies --no-daemon
# Затем копируем исходники
COPY src/ src/
RUN gradle build --no-daemon -x test

Этот трюк использует механизм кэширования слоев Docker: образ пересобирается только при изменении build.gradle.kts, а не при каждом правке кода.

2. Контейнеризация Kotlin/Native приложений

Kotlin/Native позволяет компилировать код в нативные бинарники без JVM. Это идеально для микросервисов, которым нужен минимальный размер образа и быстрый старт.

2.1. Dockerfile для Native

Для сборки нативного бинарника используем образ с Kotlin/Native компилятором. Финальный образ — scratch (пустой) или distroless.

# Стадия сборки
FROM jetbrains/kotlin-native:2.0.0 AS build
WORKDIR /app
COPY . .
RUN kotlinc-native src/main/kotlin/Main.kt -o app

# Стадия запуска: минимальный образ
FROM alpine:3.19
WORKDIR /app
COPY --from=build /app/app.kexe app
EXPOSE 8080
CMD ["./app"]

Важно: Для Alpine может потребоваться установка libc6-compat, если бинарник собран с glibc. Используйте FROM busybox:glibc для еще меньшего размера.

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

  • JVM (fat JAR + JRE): ~180-250 MB
  • JVM (distroless + slim JRE): ~80-120 MB
  • Native (Alpine): ~10-25 MB
  • Native (scratch): ~5-10 MB

Для высоконагруженных микросервисов Native-подход дает значительную экономию ресурсов.

3. Docker Compose и многомодульные проекты

Сложные Kotlin-приложения часто состоят из нескольких модулей: API, база данных, кэш. Docker Compose упрощает оркестрацию.

3.1. Пример docker-compose.yml

version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
      - REDIS_HOST=redis
    depends_on:
      - db
      - redis

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

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:

3.2. Многомодульный Gradle-проект

Если ваш проект состоит из нескольких подпроектов (например, core, web, worker), используйте Gradle Composite Builds или просто настройте settings.gradle.kts:

rootProject.name = "my-app"
include(":core", ":web", ":worker")

В Dockerfile для каждого сервиса можно копировать только нужный модуль и его зависимости:

FROM gradle:8.5-jdk21 AS build
WORKDIR /app
COPY core/ core/
COPY web/ web/
COPY build.gradle.kts settings.gradle.kts ./
RUN gradle :web:build --no-daemon -x test

4. Best practices и оптимизация

Чтобы ваши Docker-образы были безопасными и легкими, следуйте этим рекомендациям:

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

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

.git/
.idea/
build/
*.md
local.properties

4.2. Минимизируйте количество слоев

Объединяйте команды RUN в одной инструкции:

# Плохо
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

#                

Блоги

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