Software Engineer

SBT Remote Cache Recipes

SBT 1.4 introduced a remote build cache feature that can heavily reduce your local builds or parts of your CI pipeline. This short blog post demonstrates a few integration possibilites.

What is the SBT remote cache?

The idea is for a team of developers and/or a continuous integration (CI) system to share build outputs. If the build is repeatable, the output from one machine can be reused by another machine, which can make the build significantly faster.

See the SBT remote caching documentation for a bit more information.

Exampe project

I created a project with

$ sbt new scala/scala3.g8

Nexus OSS 3

The Nexus Repository Manager 3 is available as an community (OSS) and Pro edition. It provides a huge variety of repository formats.

SBT remote cache requires a raw repository with anonymous access.

Setup a Nexus test instance

Nexus OSS 3 is available as a docker image.

$ docker run -p 8081:8081 --name nexus sonatype/nexus3

The image generates a random password. You can find it by running bash inside the docker container and reading the nexus-data/admin.password file:

$ docker exec -it nexus /bin/bash
$ cat nexus-data/admin.password
3229f863-1102-411d-8cd6-e9d34e5934cd

Then go to localhost:8081, login, set a new password and open the server administration and configuration page (the cog icon at the top).

Now we create a new raw repository

  1. Click on Repositories
  2. Click Create Repository and select raw (hosted)
  3. Allow redeploy

Next we have to grant anonymous write access.

  1. Open the Roles page
  2. Add a new user role sbt-build-cache
  3. Add the following priveleges
    1. nx-repository-view-raw-sbt-build-cache-add
    2. nx-repository-view-raw-sbt-build-cache-edit
    3. nx-repository-view-raw-sbt-build-cache-read
  4. Go to the Users page
  5. Add the sbt-build-cache role to the nx-anonymous user.

Configuring SBT

You can get the repository URL from the Repositories page. There’s a copy button :)

ThisBuild / pushRemoteCacheTo := Some("Nexus OSS 3 Remote Cache" at "http://localhost:8081/repository/sbt-build-cache/")

Minio

Minio is an S3 compatible object storage system. It can be easily setup via docker or a static go binary and provides a nice web UI.

The catch is that this system can only be used internally as public access to the storage bucket is required if you don’t want to configure anything else in SBT. IMHO this isn’t a big blocker as an interal build cache system doesn’t need to be publicy available.

Setup a test minio instance

Start a minio server as described in the minio docs. I changed the data dir to /tmp/minio-data for testing purposes.

$ docker run -p 9000:9000 -e MINIO_ACCESS_KEY=minioadmin \
                          -edocker run -p 9000:9000 -e MINIO_ACCESS_KEY=minioadmin \
                          -e MINIO_SECRET_KEY=minioadmin \
                          -v /tmp/minio-data:/data minio/minio server /data MINIO_SECRET_KEY=minioadmin \
                          -v /tmp/minio-data:/data minio/minio server /data

Install the minio client mc as described in the mino client docs. After that configure an alias for the local minio instance.

# set an alias 'minio' for your local minio installation
$ mc alias set minio http://localhost:9000 minioadmin minioadmin
$ mc policy set public minio/sbt-build-cache/

Configuring SBT

ThisBuild / pushRemoteCacheTo := Some("Minio Remote Cache" at "http://localhost:9000/sbt-build-cache")

Now you can push to minio. You can see the artifacts in the web ui at localhost:9000. Credentials are minioadmin / minioadmin.

Jenkins

I haven’t tried this out, but the idea is to

  • create a local build cache in each jenkins job, e.g. in file("./.build-cache")
  • record the cache artifacts with the jenkins archiveArtifacts directive
  • use the latestSucessfullBuild URL as an additional resolver, e.g.
    remoteCacheResolvers += "Jenkins Build Cache".at("https://jenkins.your-company.com/job/my-application/lastSuccessfulBuild")
    

Why do we want to use this?

In our continous deployment pipeline we start up a canary instance of a microservice before deploying it to production. After the successful canary deployment we run a bunch of integration test against the internal APIs of this service. If these pass, we run all the integration tests of services that depend on this service against the canary instance. We call these the regression tests.

We run these regression tests by checking out the latest successful commit of the service that depends on the service that should be deployed and run sbt IntergrationTest / test. This needs to compile almost the complete application. With the help of a build cache this can run the test almost immediately!

Thanks to all the SBT folks who made this happen 💖💖