I’ve been building Scala projects for a while now, but I’ve either been writing basic, single file scripts or relying on boiler plate such as that which is provided by the Scalatra project. So I don’t really know much about building a Scala project or using the build tooling, sbt.

Today I started work on a mini-app that won’t be based on processing http requests. aka, no Scalatra. But I quickly realized I don’t know how to build such a project. So let’s figure it out.

The goal: build an sbt project that can compile code into a jar, which can then be run in a Docker container.

I’ve gained much Scala knowledge from Alvin Alexander and I turned again to him here. I based this post on two of his, but with some special modifications for a different version of scala along with my own end goal of running in docker:

First, create and cd to a new directory and lay out the project structure:

mkdir -p src/{main,test}/{java,resources,scala}
mkdir lib project target

Then create the sbt build definition file:

touch build.sbt

Inside build.sbt:

name := "sbt-starter"

version := "0.0.1"

scalaVersion := "2.12.1"

Now we need to create our app entrypoint. Since we are in the land of the jvm, this means we need a class with a main() method. Here’s a good intro to the main() method

touch src/main/scala/Main.scala

inside Main.scala

package com.lombardo.app

object Main {
  def main(args: Array[String]) {
    println("Hello from sbt starter pack!")
  }
}

That’s really it. Now we can run the program:

sbt run
...
Hello from sbt starter pack!
[success]

sbt also makes it easy to create a jar for distribution and deployment. We can then run the resulting jar using the scala compiler:

sbt package

# [info] Packaging target/scala-2.12/sbt-starter_2.12-0.0.1.jar ...

scala target/scala-2.12/sbt-starter_2.12-0.0.1.jar
# Hello from sbt starter pack!

Sweet. Now let’s Dockerize it to complete the starter pack.

touch Dockerfile

inside Dockerfile let’s copy over our jar and provide the run command:

FROM openjdk:8

ADD target/scala-2.12/sbt-starter_2.12-0.0.1.jar /usr/local/bin/target/scala-2.12/sbt-starter_2.12-0.0.1.jar

CMD ["java", "-jar", "/usr/local/bin/target/scala-2.12/sbt-starter_2.12-0.0.1.jar"]
docker build -t sbt-starter .
docker run sbt-starter:latest

what the….

# Exception in thread "main" java.lang.NoClassDefFoundError: scala/Predef$
# 	at com.lombardo.app.Main$.main(Main.scala:5)
# 	at com.lombardo.app.Main.main(Main.scala)
# Caused by: java.lang.ClassNotFoundException: scala.Predef$

What, wtf is that? I thought we added our main method to the class?

We did…but the problem is we did not set an explicit main class in our build definition. While the scala compiler can detect how to start the app, the java runtime is unable to.

No problem. There’s an sbt plugin we can use to specify our main class: sbt assembly

echo 'addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.4")' >> project/assembly.sbt

update build.sbt to the following:

name := "sbt-starter"

version := "0.0.1"

mainClass in assembly := Some("com.lombardo.app.Main")

scalaVersion := "2.12.1"

side note: the := operator in a sbt build file is apparently to type the value as a string

Now we are really ready to roll.

sbt assembly

# [info] Packaging target/scala-2.12/sbt-starter-assembly-0.0.1.jar ...

Now we just need to update our Dockerfile with the new assembly jar:

sed -i 's/sbt-starter_2.12-0.0.1.jar/sbt-starter-assembly-0.0.1.jar/g' Dockerfile

docker build -t sbt-starter .

docker run sbt-starter:latest

# Hello from sbt starter pack!

Success! I’m looking forward to using this in a upcoming side project.

Grab the repo here: https://github.com/lombardo-chcg/sbt-project-starter