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.