Introduction : pourquoi Docker et Kotlin forment le duo idéal
Le développement moderne est impensable sans la conteneurisation. Docker est devenu le standard de facto pour empaqueter, livrer et exécuter des applications. Kotlin, quant à lui, gagne rapidement en popularité grâce à sa syntaxe concise, sa compatibilité totale avec Java et son support multiplateforme. La combinaison de Docker et Kotlin permet de créer des environnements légers, évolutifs et prévisibles — d'une simple application serveur sur JVM à un binaire natif pour l'IoT.
Dans cet article, nous allons voir comment conteneuriser correctement des projets Kotlin, optimiser la taille des images et éviter les erreurs courantes. Vous pouvez adapter tous les exemples de code à votre propre projet.
1. Conteneurisation de base d'une application Kotlin/JVM
Commençons par le scénario classique : une application Kotlin fonctionnant sur JVM. Pour la construction, nous utiliserons Gradle — l'outil standard pour les projets Kotlin.
1.1. Structure du projet
Supposons que nous ayons un simple serveur HTTP basé sur Ktor. La structure du projet :
my-kotlin-app/├── build.gradle.kts├── settings.gradle.kts└── src/ └── main/ └── kotlin/ └── com/ └── example/ └── App.kt1.2. Dockerfile pour une construction multi-étapes
La pratique clé est la construction multi-étapes (multi-stage build). La première étape construit le fat JAR avec Gradle, la seconde utilise une image JRE minimale pour l'exécution.
# Étape 1 : constructionFROM gradle:8.5-jdk21 AS buildWORKDIR /appCOPY --chown=gradle:gradle . .RUN gradle build --no-daemon -x test
# Étape 2 : exécutionFROM eclipse-temurin:21-jre-alpineWORKDIR /appCOPY --from=build /app/build/libs/*.jar app.jarEXPOSE 8080ENTRYPOINT ["java", "-jar", "app.jar"]1.3. Optimisation : mise en cache des dépendances
Pour éviter de reconstruire les dépendances à chaque modification de code, divisez la commande COPY en deux parties :
FROM gradle:8.5-jdk21 AS buildWORKDIR /app# Copier d'abord uniquement les fichiers de constructionCOPY build.gradle.kts settings.gradle.kts ./COPY gradle/ gradle/RUN gradle dependencies --no-daemon# Ensuite copier les sourcesCOPY src/ src/RUN gradle build --no-daemon -x testCette astuce utilise le mécanisme de mise en cache des couches Docker : l'image n'est reconstruite que lorsque build.gradle.kts est modifié, et non à chaque modification du code.
2. Conteneurisation d'applications Kotlin/Native
Kotlin/Native permet de compiler le code en binaires natifs sans JVM. C'est idéal pour les microservices qui nécessitent une taille d'image minimale et un démarrage rapide.
2.1. Dockerfile pour Native
Pour construire un binaire natif, utilisez une image avec le compilateur Kotlin/Native. L'image finale est scratch (vide) ou distroless.
# Étape de constructionFROM jetbrains/kotlin-native:2.0.0 AS buildWORKDIR /appCOPY . .RUN kotlinc-native src/main/kotlin/Main.kt -o app
# Étape d'exécution : image minimaleFROM alpine:3.19WORKDIR /appCOPY --from=build /app/app.kexe appEXPOSE 8080CMD ["./app"]Important : Pour Alpine, il peut être nécessaire d'installer libc6-compat si le binaire est compilé avec glibc. Utilisez FROM busybox:glibc pour une taille encore plus réduite.
2.2. Comparaison des tailles d'images
- JVM (fat JAR + JRE) : ~180-250 Mo
- JVM (distroless + slim JRE) : ~80-120 Mo
- Native (Alpine) : ~10-25 Mo
- Native (scratch) : ~5-10 Mo
Pour les microservices à forte charge, l'approche Native permet une économie significative de ressources.
3. Docker Compose et projets multi-modules
Les applications Kotlin complexes sont souvent composées de plusieurs modules : API, base de données, cache. Docker Compose simplifie l'orchestration.