Docker and Kotlin: Application Containerization
Modern Kotlin development increasingly requires the use of containerization. Docker allows you to package an application together with all its dependencies into an isolated environment, which guarantees consistent behavior on a developer's local machine, test server, and in production. In this article, we will look at how to properly containerize Kotlin projects using best practices and code examples.
Why Containerize Kotlin Applications?
Kotlin runs on top of the JVM, so applications written in it inherit all the features of the Java ecosystem. The main advantages of containerization are:
- Environment Reproducibility — avoid "it works on my machine" problems.
- Dependency Isolation — versions of JDK, libraries, and system utilities are fixed in the image.
- Scalability — containers are easily deployed in Kubernetes and other orchestrators.
- Fast Deployment — Docker images are smaller in size than virtual machines.
Creating a Dockerfile for a Kotlin Application
Let's consider a typical Kotlin project using Gradle. Our build.gradle.kts might look like this:
plugins { kotlin("jvm") version "1.9.22" application}
application { mainClass.set("com.example.MainKt")}
dependencies { implementation(kotlin("stdlib")) // other dependencies}Now let's create a Dockerfile. The first option is a multistage build, which separates the compilation stage from the execution stage:
# Build stageFROM gradle:8.5-jdk21 AS buildCOPY --chown=gradle:gradle . /home/gradle/srcWORKDIR /home/gradle/srcRUN gradle build --no-daemon
# Execution stageFROM eclipse-temurin:21-jre-alpineWORKDIR /appCOPY --from=build /home/gradle/src/build/libs/*.jar app.jarEXPOSE 8080ENTRYPOINT ["java", "-jar", "app.jar"]Let's break down the key points:
- gradle:8.5-jdk21 — official image with Gradle and JDK for building.
- eclipse-temurin:21-jre-alpine — lightweight JRE image on Alpine Linux (about 80 MB).
- COPY --from=build — copy only the built JAR, excluding source code and Gradle cache.
- EXPOSE 8080 — document the port (does not open it automatically).
To build the image, run:
docker build -t kotlin-app:latest .docker run -p 8080:8080 kotlin-app:latestOptimizing Image Size
Image size is critical for deployment speed. Here are a few techniques for Kotlin applications:
1. Using jlink to Create a Custom JRE
If your application does not use all JDK modules, you can create a minimal JRE:
# Build stageFROM gradle:8.5-jdk21 AS buildCOPY . /appWORKDIR /appRUN gradle build --no-daemon
# Custom JRE creation stageFROM eclipse-temurin:21-jdk-alpine AS jre-builderRUN jlink \ --add-modules java.base,java.logging,java.naming,java.sql \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress=2 \ --output /jre
# Final stageFROM alpine:3.19ENV JAVA_HOME=/jreENV PATH="${JAVA_HOME}/bin:${PATH}"COPY --from=jre-builder /jre /jreCOPY --from=build /app/build/libs/*.jar /app.jarEXPOSE 8080ENTRYPOINT ["java", "-jar", "/app.jar"]This approach reduces the image size from ~200 MB to ~40-50 MB.
2. Using Spring Boot layertools
For Spring Boot projects (with Kotlin), you can split the JAR into layers for Docker caching:
// build.gradle.ktstasks.bootJar { layered { enabled = true }}In the Dockerfile, copy the layers separately,