Введение: почему 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/*
#