Docker и Kotlin: контейнеризация приложений на продвинутом уровне
Контейнеризация стала неотъемлемой частью современной разработки, и Kotlin — не исключение. Если вы уже освоили базовые Dockerfile и запуск простых JAR-файлов, пришло время перейти на следующий уровень. В этой статье мы рассмотрим продвинутые техники контейнеризации Kotlin-приложений: многоступенчатую сборку (multi-stage builds), оптимизацию размера образов, настройку для фреймворков Ktor и Spring Boot, а также работу с Docker Compose в production-окружении.
1. Многоступенчатая сборка Kotlin-приложений
Одна из главных проблем при контейнеризации JVM-приложений — размер финального образа. Стандартный образ OpenJDK может весить более 400 МБ. Многоступенчатая сборка позволяет отделить среду сборки от среды выполнения, используя минимальный runtime-образ.
Пример для Gradle-проекта на Kotlin
# Первый этап: сборка
FROM gradle:8.5-jdk21 AS builder
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts ./
COPY src ./src
RUN gradle build --no-daemon -x test
# Второй этап: выполнение
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Использование eclipse-temurin:21-jre-alpine вместо полного JDK-образа сокращает размер с 400+ МБ до ~120 МБ. Для Kotlin-приложений на Ktor или Spring Boot этого более чем достаточно.
Оптимизация кэширования слоёв
Чтобы ускорить повторные сборки, копируйте зависимости отдельно от исходного кода:
FROM gradle:8.5-jdk21 AS builder
WORKDIR /app
# Сначала зависимости (кэшируется, если не менялись)
COPY build.gradle.kts settings.gradle.kts ./
RUN gradle dependencies --no-daemon
# Затем исходники
COPY src ./src
RUN gradle build --no-daemon -x test
Этот приём особенно полезен в CI/CD, где каждая секунда на счету.
2. Оптимизация образов для Kotlin-приложений
Помимо выбора базового образа, существуют дополнительные техники для уменьшения размера и повышения безопасности.
Использование JLink для создания кастомной JRE
Если ваше приложение использует только часть модулей JDK, можно создать минимальную JRE:
# В builder-этапе
RUN jlink --module-path /opt/java/openjdk/jmods \
--add-modules java.base,java.logging,java.naming,java.sql \
--output /custom-jre \
--strip-debug --no-man-pages --no-header-files
# В runtime-этапе
FROM alpine:3.19
COPY --from=builder /custom-jre /opt/jre
COPY --from=builder /app/build/libs/app.jar /app/app.jar
ENV PATH="/opt/jre/bin:${PATH}"
EXPOSE 8080
CMD ["java", "-jar", "/app/app.jar"]
Размер образа может снизиться до 50-70 МБ.
Многослойная архитектура с Spring Boot
Spring Boot 3+ поддерживает разбиение fat JAR на слои. Используйте это в Dockerfile:
FROM eclipse-temurin:21-jre-alpine AS builder
WORKDIR /app
COPY build/libs/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
Теперь при изменении кода пересобирается только слой application, а зависимости и Spring Boot loader остаются закэшированными.
3. Контейнеризация Ktor-приложений
Ktor — лёгкий фреймворк для создания асинхронных серверов на Kotlin. Его контейнеризация имеет свои особенности.
Dockerfile для Ktor с Netty
FROM gradle:8.5-jdk21 AS builder
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts ./
RUN gradle dependencies --no-daemon
COPY src ./src
RUN gradle buildFatJar --no-daemon
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*-all.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xmx256m", "-Xms128m", "-jar", "app.jar"]
Обратите внимание на флаги -Xmx и -Xms. В контейнерах важно ограничивать память JVM, чтобы избежать OOM-kill.
Docker Compose для Ktor + PostgreSQL
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- DB_URL=jdbc:postgresql://db:5432/ktor_db
- DB_USER=user
- DB_PASSWORD=pass
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ktor_db
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d ktor_db"]
interval: 5s
timeout: 5s
retries: 5
Использование healthcheck гарантирует, что приложение запустится только после готовности базы данных.
4. Продвинутые техники: Jib и Distroless-образы
Если вы хотите полностью отказаться от Dockerfile, используйте плагин Jib для Gradle. Он собирает оптимизированные образы без Docker daemon.
Настройка Jib в build.gradle.kts
plugins {
id("com.google.cloud.tools.jib") version "3.4.0"
}
jib {
from {
image = "eclipse-temurin:21-jre-alpine"
}
to {
image = "myregistry/myapp:latest"
}
container {
jvmFlags = listOf("-Xmx256m", "-Xms128m")
ports = listOf("8080")
}
}
Запуск сборки: ./gradlew jibDockerBuild. Jib автомати