Running Scala Code in Docker

Sat Feb 13 2021

Alrighty, folks, this blog post is pretty straightforward from the title. We are going to be running Scala code in Docker containers. Specifically, we will be using SBT and docker-compose. SBT is a built tool primarily used by Scala developers, and docker-compose is a tool for defining docker environments.

To start, we need to create a simple Docker container that can build our scala code. From an existing Java JDK container, SBT is straightforward to install from a package manager.

FROM openjdk:8u232

ARG SBT_VERSION=1.4.1

# Install sbt
RUN \
  mkdir /working/ && \
  cd /working/ && \
  curl -L -o sbt-$SBT_VERSION.deb https://dl.bintray.com/sbt/debian/sbt-$SBT_VERSION.deb && \
  dpkg -i sbt-$SBT_VERSION.deb && \
  rm sbt-$SBT_VERSION.deb && \
  apt-get update && \
  apt-get install sbt && \
  cd && \
  rm -r /working/ && \
  sbt sbtVersion

RUN mkdir -p /root/build/project
ADD build.sbt /root/build/
ADD ./project/plugins.sbt /root/build/project
RUN cd /root/build && sbt compile

EXPOSE 9000
WORKDIR /root/build

CMD sbt compile run

There are a few things to note about this docker file. First, we are only adding the two SBT files and then running a simple SBT compile command when we build the container. This SBT compile command only used to pull in general dependencies so that the end Docker container can launch faster. Second, notice that we are exposing port 9000; this port is only for the web application I am building. Finally, note that /root/build will be the root directory for the Scala SBT application.

For reference, I include the two SBT files I’m using in this project:

build.sbt:

name := """alert-api"""
organization := "net.jrtechs"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.13.2"

resolvers += Resolver.JCenterRepository

libraryDependencies += guice

libraryDependencies += "net.katsstuff" %% "ackcord"                 % "0.16.1" //For high level API, includes all the other modules

libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test

libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "2.9.0"

plugins.sbt:

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.1")
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")

Now that we have our docker file, we can create our docker-compose script to launch the application. For this application, I am attaching the container to a simple bridged network with a port exposed.

version: '3.1'

networks:
  external-network:
    external:
      name: external-network

services:

  sbt:
    build:
      context: ./
      dockerfile: ./docker/Dockerfile
    image: sbt
    ports:
      - "9000:9000"
    volumes:
      - "./:/root/build"
    networks:
      - external-network

The main thing to note about the docker-compose script is that I placed our Dockerfile in a separate docker directory. Additionally, I’m mounting the scala project directory into the container as /root/build. The volume enables us to edit the project on our local machine while the Docker container compiles our code.

To create the docker network, we need to issue this command – it only needs to be run once.

docker network create -d bridge external-network

To run the project, we can use the docker-compose up command:

docker-compose run

To use the SBT shell, we need to open a terminal to the running container. After we have the shell, we can issue all of our standard SBT commands. To properly forward the required ports when using docker-compose run, you need to pass in the “–service-ports” flag.

docker-compose run --service-ports sbt /bin/bash 
sbt
compile
run

Since the location where we launch docker-compose is in the same directory as all of our scala code and build artifacts, we must create a “.dockerignore” file. Otherwise, Docker will scan the entire directory before building the container – causing massive frustration. The "**" in the Docker ignore file tells Docker to ignore everything, and the “!” tells Docker to include that file. Alternatively, we could have just excluded the target build directory.

**
!docker
!build.sbt
!project/plugins.sbt

That is it. I enjoy the docker approach towards developing Scala applications since it keeps the environment consistent across machines. As someone who enjoys distro-hopping, it is nice not having to figure out how to install Java, SBT, Scala, and countless other development environments on every operating system I use. I only need to install a text editor and Docker to develop projects with vastly different build environments and configurations. All the complexity with setting up the environment can get relegated to the Docker container.

This approach went over a container geared towards Scala development. For production, I would recommend that you use this SBT image to build a fat JAR, and then copy it into a lightweight JRE container using Docker’s multi-stage build functionality.