How to speed up sbt scala build in docker? - scala

I have docker file with:
FROM amazonlinux:2017.03
ENV SCALA_VERSION 2.11.8
ENV SBT_VERSION 0.13.13
# Install Java8
RUN yum install -y java-1.8.0-openjdk-devel
# Install Scala and SBT
RUN yum install -y https://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.rpm
RUN yum install -y https://dl.bintray.com/sbt/rpm/sbt-0.13.13.rpm
RUN sbt sbtVersion
COPY . /root
WORKDIR /root
# Exposing port 80
EXPOSE 80
RUN sbt compile
CMD sbt run
And sbt configuration file with:
name := "hello"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.5"
libraryDependencies += "com.fasterxml.jackson.module" % "jackson-module-scala_2.11" % "2.9.5"
Each time when I build docker container sbt download jackson library anew. How can I speed up this process, may execute part of sbt file before compilation.
Before I have add RUN sbt sbtVersion to Dockerfile sbt have download itself completely and after I add this command have cached and not run each time when I build docker container.
May be there is same tricks with caching in docker downloading libraries in sbt?

First, you don't need to install the scala RPM as SBT itself downloads Scala for you (whichever version is configured in your build).
Second, each RUN command creates a new layer which you want to avoid usually. Combine them:
RUN cmd1 \
&& cmd2 \
&& cmd3
Why do you want to build an image for each of your builds? That seems wasteful. Usually you build your stuff outside of a Docker image and only package up the results.
My advice would be to use the sbt-native-packager SBT plugin with its Docker integration to simply build an image from your artefacts after you have build them. That way you would only need a JRE in your image, not a JDK, not SBT. Also, you would not need to wait for SBT to initialize when starting your image.
You could use multi-stage builds if you have a new Docker version installed.

You can reference to travis's solution:
.travis.yaml:
# These directories are cached to S3 at the end of the build
cache:
directories:
- $HOME/.ivy2/cache
- $HOME/.sbt/boot/
Travis will backup the above two folders to s3, then every time before user rebuild, it will fetch the two folders from cache.
So, we can know the possible way for you to not download jackson library again could be separate the .ivy2 & .sbt folder and put them in something like a cache.
For your situation, I think the best solution is store these in a base image, the base image's Dockerfile could simply same as the one you are using now(of course, you can simple it, just add jackson library define in build.sbt, the aim just want the .ivy2 have the jackson library), then E.g. tag it as mybaseimage:v1
Then your new project's Dockerfile could use base mybaseimage:v1, as mybaseimage already have the library in .ivy2& .sbt, so no need to download jackson again every time you build your project's Dockerfile.
The solution maybe ugly, but I think could act as a workaround, just FYI.

Related

How to add missing SNAPSHOT dependency to Heroku?

I have a Scala Play 2.7.x application which I deploy to Heroku. However, the build fails due to a missing dependency "com.github.tototoshi" %% "play-joda-routes-binder" % "1.3.1-SNAPSHOT" and that's correct because this one I built locally. How do I make this missing dependency available to Heroku?
The dependency I need is this one https://github.com/tototoshi/play-joda-routes-binder but it had a bug which I fixed here https://github.com/tototoshi/play-joda-routes-binder/pull/6. However, the author of that project seems afk for several months now. I can build my PR locally but how do I add it to Heroku for my project to work?
You can put the compiled jar into a subfolder in the project for example: /lib. Sbt will automatically look for jars in this directory. If you want to configure it as something else you can define the key unmanagedBase:
unmanagedBase := baseDirectory.value / "custom_lib"
There's more documentation about sbt lib management here: https://www.scala-sbt.org/1.x/docs/Library-Dependencies.html.
Also playframework documents this, but it's basically the same:
https://www.playframework.com/documentation/2.7.1/SBTDependencies
Another solution is to not use the automatic deployment with connection to Github but rather the SBT Heroku CLI and then build / deploy locally and simply upload the binary artifacts to Heroku like so:
sbt stage deployHeroku
heroku open --app myapp
after adding the following two entries into the project/plugins.sbt file (latest versions as of today):
addSbtPlugin("com.heroku" % "sbt-heroku" % "2.1.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.4.1")

sbt-native-packager scala play dockerfile not running with default config

I'm trying to use the sbt-native-packager to produce a Docker image of my Scala play app, I followed the steps described at http://www.scala-sbt.org/sbt-native-packager/formats/docker.html
This is my configuration:
on my plugins.sbt I added the dependency for sbt native packager:
// SBT Native
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.2.1")
on my build.sbt I added the plugins for Universal and Docker:
.enablePlugins(PlayScala, JavaAppPackaging)
I also added some extra properties:
javaOptions in Universal ++= Seq(
// JVM memory tuning
"-J-Xmx1024m",
"-J-Xms512m",
// Since play uses separate pidfile we have to provide it with a proper path
// name of the pid file must be play.pid
s"-Dpidfile.path=/var/run/${packageName.value}/play.pid",
// Use separate configuration file for production environment
s"-Dconfig.file=/usr/share/${packageName.value}/conf/production.conf",
// Use separate logger configuration file for production environment
s"-Dlogger.file=/usr/share/${packageName.value}/conf/logback.xml"
)
// exposing the play ports
dockerExposedPorts in Docker := Seq(9000, 9443)
Then I generate the docker image using the plugin and SBT CLI:
docker:publishLocal
the dockerfile gets generated at ./target/docker/Dockerfile
when I inspect the file I see:
FROM openjdk:latest
WORKDIR /opt/docker
ADD opt /opt
RUN ["chown", "-R", "daemon:daemon", "."]
USER daemon
ENTRYPOINT ["bin/root"]
CMD []
which doesn't seem to contain all the necessary steps to run the app, when I use docker build . I get :
java.nio.file.NoSuchFileException: /var/run/root/play.pid
It seems like the Dockerfile is missing some steps where it should mkdir /var/run/{APP_NAME}/
(* creating folder inside docker container instance)
and chown that folder in order for play to create the PID file.
how to fix the above error ?
What's the error message when starting the docker image and how do you start it?
Then there are a couple of notable things.
play ships with native-packager
You shouldn't have to add any plugin, but only configure docker relevant stuff. You already linked the correct documentation for the package format ( docker ).
Archetypes Vs Formats
Your configuration won't work without the play plugin. Take a look at http://www.scala-sbt.org/sbt-native-packager/archetypes/java_app/index.html which explains how to configure a simple application build.
I'd also recommend to read the format and Archetypes section here:
http://www.scala-sbt.org/sbt-native-packager/introduction.html#archetype-plugins
Native docker build
Native-packager currently generates two docker files, which is confusing and not relevant. Sorry for that confusion. We plan to remove the redundant docker file.
Simply go one level deeper and run the docker build command.
Hope that helps,
Muki

Publish RPM to remote YUM repository with SBT

In our team we build RPM file containing our app, and then we have a bash/cURL script to publish it to the remote YUM repository. Essentially it is a HTTP POST with form data:
curl ${curl_opts} -F "rpm=#$RPM;type=application/x-redhat-package-manager"
Now I want to replace this script with the SBT, since it's inconvenient to have it from deployment perspective. I've tried to find some SBT plugin that can handle this for me, but to no avail. Is there such a thing?
P.S. I guess, another approach would be to perform such HTTP POST from SBT directly, but that would be the other question.
You can use SBT Native Packager (RPM) to generate the RPM and configure sbt-package-courier to deploy to your repository.
In your plugins.sbt file add:
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.4")
addSbtPlugin("io.sysa" % "sbt-package-courier" % "0.2.0")
Then for publishing:
enablePlugins(RpmArtifactoryDeployPlugin)
rpmArtifactoryUrl in Rpm := "https://repo.acme.corp/artifactory"
rpmArtifactoryCredentials in Rpm := Some(Credentials(Path.userHome / ".ivy2"/ ".credentials"))
rpmArtifactoryRepo in Rpm := "rpm-repo"
rpmArtifactoryPath in Rpm := s"pool/${packageName.value}"
publish in Rpm := (rpmArtifactoryPublish in Rpm).value
And finally
sbt rpm:publish
I believe you already have done packaging part.

Prefetch SBT versions, Scala, and Ivy Resources with SBT for creating an image

I wish to create a docker image with several SBT components built in (docker part not relevant). If I just install sbt from deb or rpm and not do anything, the first time it is invoked it still has to download the internet before it's able to start.
Thus, I need to be able to specify the following:
1 SBT Versions - e.g. 0.12, 0.13.1, 0.13.2, ...
2 Scala Versions - 2.9.1, 2.10.4, 2.11.5, ...
3 Commonly used libraries - play framework, etc.
I'm wondering what's the best way to ensure those are already pre-cached.
Here is what I do in my Dockerfile to prefetch multiple scala versions :
RUN echo 'crossScalaVersions := Seq("2.10.6", "2.11.7")' > build.sbt \
&& echo 'object Hi { def main(args: Array[String]) = println("Done") }' > src.scala \
&& sbt "+run" \
&& rm build.sbt src.scala
You can easily customize this further by specifying the sbt version on cli ( sbt -Dsbt.version=0.x.y "+run" ), or add some libraryDependencies to the build.sbt.
HTH
Well, if you create one project which has everything you want listed as dependency -- including SBT and Scala versions, and then run update to retrieve all these dependencies, they'll all be cached on .ivy2\cache. This should serve for pretty much everything, but SBT itself seems to have tons of separate dependencies as seen here, or on your local disk at the ivy2 cache.

sbt retrieveManaged doesn't pick up jars produced using publish-local

Suppose I have a project in which I've used sbt publish-local to generate local copies of the jar files in ~/.ivy2/local.
Another sbt project on the same machine can successfully locate these jars to satisfy dependencies. However, if in that project I use the option retrieveManaged := true, sbt never copies the jars out of ~/.iv2/local into the lib_managed directory.
How can I make this happen? (Is this behaviour intended?)
(I'm using sbt 0.12.1.)
I am using the same setup. When I update my locally published dependencies, I remove the managed jars first and then run the update:
$ rm -r lib_managed/jars/my-package-prefix
$ sbt test:compile
(test:compile will figure out that the jars are missing and do the update by itself).
In older versions of sbt I believe there was an issue when using -SNAPSHOT versions, but I haven't seen this problem anymore with sbt 0.12.
In rare situations, something messes up with the Ivy cache and somehow you cannot enforce the updates. This is perhaps the case when corrupt Ivy meta data files have been used. Then the only solution is to wipe all occurrences of your dependency from ~/.ivy2/local and ~/.ivy2/cache, re-publish, and re-update.