Creating Docker Container
Publishing a Ktor application to docker is very easy and only takes a few steps:
- Install Docker
- A JAR packaging tool
In this page we will guide you through creating a docker image and publishing an application to it.
Table of contents:
In this tutorial, we will use the Gradle shadow plugin.It packages all the output classes, resources, and all the required dependencies into a single JAR file,and appends a manifest file to tell Java which is the entry-point main class containing the main method.
First, you need to add the shadow plugin dependency in your file:
After that, you have to apply it, along with the application plugin:
apply plugin: "com.github.johnrengelman.shadow"
apply plugin: 'application'
Then specify the main class, so it knows what to run when running the java’s JAR inside Docker:
mainClassName = 'org.sample.ApplicationKt'
The string is the fully qualified name of the class containing your main
function. When main
function is a top-levelfunction in a file, the class name is the file name with the Kt
suffix. In the example above, main
function is in thefile Application.kt
in package org.sample
.
Finally, you have to configure the shadow plugin:
shadowJar {
baseName = 'my-application'
classifier = null
version = null
}
Now you can run ./gradlew build
to build and package your application.You should get my-application.jar
in build/libs
folder.
So a full build.gradle
file would look like this:
build.gradle
buildscript {
ext.kotlin_version = '1.3.61'
ext.ktor_version = '1.3.0'
ext.logback_version = '1.2.3'
ext.slf4j_version = '1.7.25'
repositories {
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.github.jengelman.gradle.plugins:shadow:2.0.1"
}
}
apply plugin: "com.github.johnrengelman.shadow"
apply plugin: 'application'
mainClassName = "io.ktor.server.netty.EngineMain"
sourceSets {
main.resources.srcDirs = [ 'resources' ]
}
repositories {
jcenter()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "io.ktor:ktor-server-netty:$ktor_version"
compile "io.ktor:ktor-html-builder:$ktor_version"
compile "ch.qos.logback:logback-classic:$logback_version"
}
kotlin.experimental.coroutines = 'enable'
shadowJar {
baseName = 'my-application'
classifier = null
version = null
}
resources/application.conf
src/HelloApplication.kt
package io.ktor.samples.hello
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.html.*
import io.ktor.routing.*
import kotlinx.html.*
fun Application.main() {
install(DefaultHeaders)
routing {
get("/") {
call.respondHtml {
head {
title { +"Ktor: jetty" }
}
body {
p {
}
}
}
}
}
}
You can check this full example at the ktor-samples repository.
In the root folder of your project create a file named Dockerfile
with the following contents:
Dockerfile
FROM openjdk:8-jre-alpine
ENV APPLICATION_USER ktor
RUN adduser -D -g '' $APPLICATION_USER
RUN mkdir /app
RUN chown -R $APPLICATION_USER /app
USER $APPLICATION_USER
COPY ./build/libs/my-application.jar /app/my-application.jar
WORKDIR /app
CMD ["java", "-server", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-XX:InitialRAMFraction=2", "-XX:MinRAMFraction=2", "-XX:MaxRAMFraction=2", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "my-application.jar"]
Let’s see what is what:
FROM openjdk:8-jre-alpine
This line tells Docker to base an image on a pre-built image with Alpine Linux. You can use other images from . Alpine Linux benefit is that the image is pretty small. We also select JRE-only image since we don’t need to compile code on the image, only run precompiled classes.
RUN mkdir /app
COPY ./build/libs/my-application.jar /app/my-application.jar
WORKDIR /app
These lines copy your packaged application into the Docker image and sets the working directory to where we copied it.
Build an application package:
./gradlew build
Build and tag an image:
docker build -t my-application .
Start an image:
docker run -m512M --cpus 2 -it -p 8080:8080 --rm my-application
With this command, we start Docker in a foreground mode. It will wait for the server to exit, itwill also respond to Ctrl+C
to stop it. -it
instructs Docker to allocate a terminal (tty) to pipe the stdoutand to respond to the interrupt key sequence.
Since our server is running in an isolated container now, we should tell Docker to expose a port so we canactually access the server port. Parameter -p 8080:8080
tells Docker to publish port 8080 from inside a container as a port 8080 on a localmachine. Thus, when you tell your browser to visit localhost:8080
it will first reach out to Docker, and it will bridgeit into internal port 8080
for your application.
You can adjust memory with -m512M
and number of exposed cpus with —cpus 2
.
By default a container’s file system persists even after the container exits, so we supply —rm
option to preventgarbage piling up.
For more information about running a docker image please consult docker run documentation.
Once your application is running locally successfully, it might be a time to deploy it:
docker tag my-application hub.example.com/docker/registry/tag
These commands will tag your application for a registry and push an image. Of course, you need to replace hub.example.com/docker/registry/tag
with an actual URL for your registry.
You can check a at the ktor-samples repository.