Posted on • Originally published atblog.baens.net on
Step-by-step: Kotlin and Docker
This was originally published onmy personal blog
This is a step-by-step guide on how to get Kotlin running inside of a Docker container. We will get a basic Kotlin project running locally then demonstrate how to get that same project being built inside and running inside of a Docker container.
The companion repository is here.
Step 0: Prerequisites
I am going to assume you have the following installed and already running. We will be using the local gradle wrapper instance after the initial setup, but you do need it for the initial creation.
The other one I am going to assume is having a running docker instance.
- Java (JDK 1.8.0_144)
- Gradle (to create the initial wrapper)
- Docker (17.12.0-ce-mac49 (21995))
Step 1: Gradle
The goal of this step will be to get a gradle wrapper up and running so that we can lock in the gradle version for everyone that will be using this repository.
Run the following:
> gradle wrapper --gradle-version 4.5
Verify it works by running:
> ./gradlew -v------------------------------------------------------------Gradle 4.5------------------------------------------------------------Build time: 2018-01-24 17:04:52 UTCRevision: 77d0ec90636f43669dc794ca17ef80dd65457becGroovy: 2.4.12Ant: Apache Ant(TM) version 1.9.9 compiled on February 2 2017JVM: 1.8.0_144 (Oracle Corporation 25.144-b01)
That's it! You can double check that commit that I have associated with this step to see what I add to a git repository for this but this really is an easy step.
Step 2: Kotlin Hello World locally
Our end goal for this step will be to make it print "Hello World" from the./gradlew run
command.
Let's first setup our tool chain. This will be just a gradle file with all of the plugins and dependencies that we will need.
build.gradle:
/** * This section is for all of the plugins we need to make this work. * Kotlin, the fat jar builder, and flag it * as an application so `./gradlw run` will work */plugins{id'org.jetbrains.kotlin.jvm'version'1.2.21'id'com.github.johnrengelman.shadow'version'2.0.2'}applyplugin:'application'/** * Standard dependency section for gradle. * Define the kotlin standard libray for * Java 8. */repositories{mavenCentral()}dependencies{compile'org.jetbrains.kotlin:kotlin-stdlib-jre8'}/** * Personal preference. I hate having src/main/kotlin * be the root, so I change it that 'src' * is the root of my source directory. */sourceSets{main.kotlin.srcDirs+='src'}// Define the main startup class and jar namemainClassName='MainKt'archivesBaseName='step-by-step-kotlin'// tell the jar which class to startup in.jar{manifest{attributes'Main-Class':'MainKt'}}
Let's go through some of those lines again and draw out whats going on.
Lines 6 - 11: These are just plugin lines. Gradle has a couple of ways to define plugins. One is called theplugin DSL
the other is calledscript plugin
. I'm not going to try and explain the difference here, please go readthe plugins doc to get a better idea. One plugin to note is theshadow
plugin. This one is to create Fat Jars, or in other words, jars that contain every dependency that is needed to run. Instead of having to make sure things are on your classpath or some other nonsense, I can just give you this jar file, and it will run. This will make it running inside of docker easier.
Line 24: Here we are defining the kotlin standard for Java 8. This is newer in Kotlin 1.1 and you can read about ithere. There are a few ways to do this, but this was specific to what was needed for Java 8. I've run into issues using the backwards compatible one and try and use the Java 8 one so that the newer features actually work
Lines 32 - 34: This is entirely optional, but this is how I prefer to setup my projects. I hate the default ofsrc/main/java
or something silly like that. That is 3 folders I have to navigate just to get to my source. PLUS then I have to navigate down through the folders of my namespace. Rarely do projects have to have all of these different types of languages so you can eliminate thejava
folder at the very least. And 2nd, having to maintain atest
source folder and amain
source folder is the PITA. I much prefer to have my tests and source side by side. It just makes it easier to find my tests that are associated with each file. Again, this is all personal choice but I've been doing this long enough to feel strongly about this one now. Give me one folder with all my source and don't make me nest things that I don't want too!
Line 37: This is a simple line but I did want to call out something.MainKt
is special because it will be the name of the file withkt
at the end of it. I will explain a little bit more when we get to that source. But this is a convention Kotlin has when there is just a method inside of the file. It will automatically enclose that method in a class for the JVM to use. So be careful about thatkt
.
Now for the actual, very simple, hello world code.
src/Main.kt:
funmain(args:Array<String>){println("Hello World")}
I want to point out and congratulate the Kotlin team on this. If you have ever done any Java programming, you know that you always need a class per file. Kotlin has a few conventions to make certain things easier and I think this is brilliant. I love things like this that just remove the pomp and circumstance. This is especially helpful when trying to get new developers into the language. Every time I've had to teach someone Java, thepublic class Whatever
was foreign and we had to go down a rabbit hole even before we had anything running.
You should now be able to run./gradlew run
and see this:
>./gradlew run> Task :runHello WorldBUILD SUCCESSFUL in 5s2 actionable tasks: 2 executed
Step 3: Kotlin hello world inside docker
Update as of 2018-02-15: Thammachart Chinvarapon was gracious enough to point out that I was using the wrong Java docker container. I corrected this and moved it to the openjdk container. Thanks Thammachart!
The next step will be to run this inside of a docker container. While this would be fairly straight forward, I want to demonstrate how to separate your build docker image from your running docker image. While this adds a little bit of complexity, this will actually be more like how you really use these kind of things.
Since we have everything working, we just need to get a Dockerfile created and running.
Dockerfile
ARG VERSION=8u151FROMopenjdk:${VERSION}-jdkasBUILDCOPY . /srcWORKDIR /srcRUN./gradlew--no-daemon shadowJarFROM openjdk:${VERSION}-jreCOPY --from=BUILD /src/build/libs/step-by-step-kotlin-all.jar /bin/runner/run.jarWORKDIR /bin/runnerCMD ["java","-jar","run.jar"]
Line 3 - 9: This is the "builder" section.
Line 9 - 14: This it the section that will be actually running.
This is an awesome way to wrap up in one docker file how to build and create the image inside of itself, as well as run it. What I try and do is make it so you can create this docker image from nearly anywhere. This builder image includes all of the tools needed to build things out. And since we have this gradle wrapper, the toolchain is also there.
The 2nd part is what is actually running. TheCOPY --from=BUILD
is the magic that we can pull from the last image (note:BUILD
is from line 3as BUILD
).
I'm not going to go too much of what exactly docker is doing but do want to point out a couple of quick things. We basically do a copy, setup what is the working directory (thinkcd
if you were using command line), then do the work or run.
Run this and the image will build and run:
docker build-t kotlin.&& docker run kotlin
Top comments(1)
For further actions, you may consider blocking this person and/orreporting abuse