Docker und Kotlin: Containerisierung von Anwendungen für Entwickler

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum Docker und Kotlin das ideale Paar sind

Moderne Entwicklung ist ohne Containerisierung undenkbar. Docker hat sich zum De-facto-Standard für das Verpacken, Ausliefern und Ausführen von Anwendungen entwickelt. Kotlin wiederum gewinnt dank seiner prägnanten Syntax, vollständigen Kompatibilität mit Java und Unterstützung für Multiplattform-Entwicklung rasant an Popularität. Die Kombination von Docker und Kotlin ermöglicht es, leichtgewichtige, skalierbare und vorhersagbare Umgebungen zu schaffen – von einer einfachen Serveranwendung auf der JVM bis hin zu einem nativen Binary für IoT.

In diesem Artikel werden wir uns ansehen, wie man Kotlin-Projekte richtig containerisiert, die Image-Größe optimiert und typische Fehler vermeidet. Alle Codebeispiele können Sie an Ihr eigenes Projekt anpassen.



1. Basis-Containerisierung einer Kotlin/JVM-Anwendung

Beginnen wir mit dem klassischen Szenario: einer Kotlin-Anwendung, die auf der JVM läuft. Für den Build verwenden wir Gradle – das Standardwerkzeug für Kotlin-Projekte.



1.1. Projektstruktur

Nehmen wir an, wir haben einen einfachen HTTP-Server mit Ktor. Die Projektstruktur sieht so aus:

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


1.2. Dockerfile für einen Multi-Stage-Build

Eine entscheidende Praxis ist der Multi-Stage-Build. Die erste Stufe erstellt mit Gradle ein fat JAR, die zweite verwendet ein minimales JRE-Image für den Start.

# Stufe 1: BuildFROM gradle:8.5-jdk21 AS buildWORKDIR /appCOPY --chown=gradle:gradle . .RUN gradle build --no-daemon -x test

# Stufe 2: AusführungFROM eclipse-temurin:21-jre-alpineWORKDIR /appCOPY --from=build /app/build/libs/*.jar app.jarEXPOSE 8080ENTRYPOINT ["java", "-jar", "app.jar"]


1.3. Optimierung: Caching von Abhängigkeiten

Um Abhängigkeiten nicht bei jeder Code-Änderung neu zu bauen, teilen Sie den COPY-Befehl in zwei Teile auf:

FROM gradle:8.5-jdk21 AS buildWORKDIR /app# Zuerst nur die Build-Dateien kopierenCOPY build.gradle.kts settings.gradle.kts ./COPY gradle/ gradle/RUN gradle dependencies --no-daemon# Dann die Quelltexte kopierenCOPY src/ src/RUN gradle build --no-daemon -x test

Dieser Trick nutzt den Layer-Caching-Mechanismus von Docker: Das Image wird nur neu gebaut, wenn sich build.gradle.kts ändert, nicht bei jeder Code-Änderung.



2. Containerisierung von Kotlin/Native-Anwendungen

Kotlin/Native ermöglicht es, Code ohne JVM in native Binaries zu kompilieren. Das ist ideal für Microservices, die eine minimale Image-Größe und einen schnellen Start benötigen.



2.1. Dockerfile für Native

Für den Build des nativen Binaries verwenden wir ein Image mit dem Kotlin/Native Compiler. Das finale Image ist scratch (leer) oder distroless.

# Build-StufeFROM jetbrains/kotlin-native:2.0.0 AS buildWORKDIR /appCOPY . .RUN kotlinc-native src/main/kotlin/Main.kt -o app

# Ausführungsstufe: minimales ImageFROM alpine:3.19WORKDIR /appCOPY --from=build /app/app.kexe appEXPOSE 8080CMD ["./app"]

Wichtig: Für Alpine kann die Installation von libc6-compat erforderlich sein, wenn das Binary mit glibc erstellt wurde. Verwenden Sie FROM busybox:glibc für eine noch kleinere Größe.



2.2. Vergleich der Image-Größen

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

Für hochlastige Microservices bietet der Native-Ansatz eine erhebliche Ressourceneinsparung.



3. Docker Compose und Multi-Modul-Projekte

Komplexe Kotlin-Anwendungen bestehen oft aus mehreren Modulen: API, Datenbank, Cache. Docker Compose vereinfacht die Orchestrierung.



3.1. Beispiel docker-compose.

Blogs

Buchempfehlungen