Images containing Scala applications
Users are typically migrating from scala_image in rules_docker.
You can request the *_deploy.jar output of a scala_binary target, which is a single, self-contained launcher that includes all the dependencies. This can then be added to a container with a base image such as gcr.io/distroless/java17 and then executed directly as java -jar <your jar>
.
Example
For this example, we will use App.scala
like below:
App.scala
object App {
def main(args: Array[String]): Unit = {
println("Hello, world!")
}
}
In this example, I will not use bzlmod and fall back to the WORKSPACE
file, as rules_scala
doesn't support bzlmod yet. This file setups
the rules_scala
according to the documentation so that we can build scala targets. Next, it configures aspect_bazel_lib
so that we can have access to tar
rule needed later. Finally, it configures rules_oci
and pulls the base image with Java 17.
WORKSPACE
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "bazel_skylib",
sha256 = "66ffd9315665bfaafc96b52278f57c7e2dd09f5ede279ea6d39b2be471e7e3aa",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.4.2/bazel-skylib-1.4.2.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.2/bazel-skylib-1.4.2.tar.gz",
],
)
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
bazel_skylib_workspace()
http_archive(
name = "io_bazel_rules_scala",
sha256 = "3b00fa0b243b04565abb17d3839a5f4fa6cc2cac571f6db9f83c1982ba1e19e5",
strip_prefix = "rules_scala-6.5.0",
url = "https://github.com/bazelbuild/rules_scala/releases/download/v6.5.0/rules_scala-v6.5.0.tar.gz",
)
load("@io_bazel_rules_scala//:scala_config.bzl", "scala_config")
scala_config(scala_version = "2.13.12")
load("@io_bazel_rules_scala//scala:scala.bzl", "scala_repositories")
scala_repositories()
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
rules_proto_dependencies()
rules_proto_toolchains()
load("@io_bazel_rules_scala//scala:toolchains.bzl", "scala_register_toolchains")
scala_register_toolchains()
load("@io_bazel_rules_scala//testing:scalatest.bzl", "scalatest_repositories", "scalatest_toolchain")
scalatest_repositories()
scalatest_toolchain()
http_archive(
name = "aspect_bazel_lib",
sha256 = "6d758a8f646ecee7a3e294fbe4386daafbe0e5966723009c290d493f227c390b",
strip_prefix = "bazel-lib-2.7.7",
url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.7.7/bazel-lib-v2.7.7.tar.gz",
)
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies", "aspect_bazel_lib_register_toolchains")
# Required bazel-lib dependencies
aspect_bazel_lib_dependencies()
# Register bazel-lib toolchains
aspect_bazel_lib_register_toolchains()
http_archive(
name = "rules_oci",
sha256 = "647f4c6fd092dc7a86a7f79892d4b1b7f1de288bdb4829ca38f74fd430fcd2fe",
strip_prefix = "rules_oci-1.7.6",
url = "https://github.com/bazel-contrib/rules_oci/releases/download/v1.7.6/rules_oci-v1.7.6.tar.gz",
)
load("@rules_oci//oci:dependencies.bzl", "rules_oci_dependencies")
rules_oci_dependencies()
load("@rules_oci//oci:repositories.bzl", "LATEST_CRANE_VERSION", "oci_register_toolchains")
oci_register_toolchains(
name = "oci",
crane_version = LATEST_CRANE_VERSION,
)
# You can pull your base images using oci_pull like this:
load("@rules_oci//oci:pull.bzl", "oci_pull")
oci_pull(
name = "distroless_java",
digest = "sha256:161a1d97d592b3f1919801578c3a47c8e932071168a96267698f4b669c24c76d",
image = "gcr.io/distroless/java17",
)
Now, let's create BUILD.bazel step by step. First, create a scala_binary
target for our app. It is safe to add dependencies, but they were omitted here for simplicity.
BUILD.bazel
load("@io_bazel_rules_scala//scala:scala.bzl", "scala_binary")
scala_binary(
name = "app",
srcs = ["App.scala"],
main_class = "App",
)
After that, we can package that binary into a layer using tar
load("@aspect_bazel_lib//lib:tar.bzl", "tar")
tar(
name = "layer",
srcs = [":app_deploy.jar"],
)
Next, construct the image from base image and our new layer, using oci_image
rule. The entrypoint is set to java -jar /app_deploy.jar
so that the image can be run directly.
load("@rules_oci//oci:defs.bzl", "oci_image")
oci_image(
name = "image",
base = "@distroless_java",
entrypoint = ["java", "-jar", "/app_deploy.jar"],
tars = [":layer"],
)
Finally, create a tarball from oci_image
that can be loaded by a runtime such as docker. We specify repo_tags
so that the image can be loaded by a registry.
load("@rules_oci//oci:defs.bzl", "oci_load")
oci_load(
name = "tarball",
image = ":image",
repo_tags = ["my-repository:latest"],
)
Test if it works:
$ bazel run //:tarball
...
$ docker run --rm my-repository:latest
Hello, world!
Complete BUILD.bazel
file
BUILD.bazel
load("@io_bazel_rules_scala//scala:scala.bzl", "scala_binary")
load("@aspect_bazel_lib//lib:tar.bzl", "tar")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load")
scala_binary(
name = "app",
srcs = ["App.scala"],
main_class = "App",
)
tar(
name = "layer",
srcs = [":app_deploy.jar"],
)
oci_image(
name = "image",
base = "@distroless_java",
entrypoint = ["java", "-jar", "/app_deploy.jar"],
tars = [":layer"],
)
oci_load(
name = "tarball",
image = ":image",
repo_tags = ["my-repository:latest"],
)