I am trying to produce a docker image using the rules_oci
bazel repo.
I am using a custom python toolchain I have registered in my WORKSPACE
. The following code builds a docker image that contains the python toolchain and run_api_server.py
and its relevant dependencies.
My questions are:
- How can I correctly set the python toolchain and
run_api_server
as entrypoint of the docker image such that all bazel paths work out of the box? - How can I isolate the relevant code that goes on the bazel project to a separate folder
/app
on the docker image?
BUILD
:
load("@rules_oci//oci:defs.bzl", "oci_tarball")
load("@rules_python//python:defs.bzl", "py_binary")
load(":py_image.bzl", "py_docker_image")
py_binary(
name = "run_api_server",
srcs = "run_api_server.py",
deps = [
"run_api_server.py",
"//myproject:my_py_library",
],
)
py_docker_image(
name = "run_api_server_docker",
base = "@ubuntu_2204_base",
binary = ":run_api_server",
)
py_image.bzl
(taken from https://github.com/aspect-build/bazel-examples/blob/main/oci_python_image/py_layer.bzl):
load("@aspect_bazel_lib//lib:tar.bzl", "mtree_spec", "tar")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_tarball")
# match *only* external repositories that have the string "python"
# e.g. this will match
# `/hello_world/hello_world_bin.runfiles/rules_python~0.21.0~python~python3_9_aarch64-unknown-linux-gnu/bin/python3`
# but not match
# `/hello_world/hello_world_bin.runfiles/_main/python_app`
PY_INTERPRETER_REGEX = "\\.runfiles/.*python.*-.*"
# match *only* external pip like repositozries that contain the string "site-packages"
SITE_PACKAGES_REGEX = "\\.runfiles/.*/site-packages/.*"
def py_layers(name, binary):
"""
Create three layers for a py_binary target: interpreter, third-party dependencies, and application code.
This allows a container image to have smaller uploads, since the application layer usually changes more
than the other two.
Args:
name: prefix for generated targets, to ensure they are unique within the package
binary: a py_binary target
Returns:
a list of labels for the layers, which are tar files
"""
# Produce layers in this order, as the app changes most often
layers = ["interpreter", "packages", "app"]
# Produce the manifest for a tar file of our py_binary, but don't tar it up yet, so we can split
# into fine-grained layers for better docker performance.
mtree_spec(
name = name + ".mf",
srcs = [binary],
)
native.genrule(
name = name + ".interpreter_tar_manifest",
srcs = [name + ".mf"],
outs = [name + ".interpreter_tar_manifest.spec"],
cmd = "grep '{}' $< >$@".format(PY_INTERPRETER_REGEX),
)
native.genrule(
name = name + ".packages_tar_manifest",
srcs = [name + ".mf"],
outs = [name + ".packages_tar_manifest.spec"],
cmd = "grep '{}' $< >$@".format(SITE_PACKAGES_REGEX),
)
# Any lines that didn't match one of the two grep above
native.genrule(
name = name + ".app_tar_manifest",
srcs = [name + ".mf"],
outs = [name + ".app_tar_manifest.spec"],
cmd = "grep -v '{}' $< | grep -v '{}' >$@".format(SITE_PACKAGES_REGEX, PY_INTERPRETER_REGEX),
)
result = []
for layer in layers:
layer_target = "{}.{}_layer".format(name, layer)
result.append(layer_target)
tar(
name = layer_target,
srcs = [binary],
mtree = "{}.{}_tar_manifest".format(name, layer),
)
return result
def py_oci_image(name, binary, tars = [], **kwargs):
"""
Wrapper around oci_image that splits the py_binary into layers.
Note you need to wrap the result of this rule in oci_tarball to produce
an image that can be loaded by docker
Args:
name: name for the target
binary: a py_binary target
tars: extra docker layers, apart from `binary` dependencies
kwargs: see oci_image https://github.com/bazel-contrib/rules_oci/blob/main/docs/image.md
"""
oci_image(
name = name,
tars = tars + py_layers(name, binary),
**kwargs
)
def py_docker_image(name, binary, repo_tags = [], **kwargs):
py_oci_image(
name = name + ".image",
binary = binary,
**kwargs,
)
# Wrap in oci_tarball to produce an image that can be loaded by docker
oci_tarball(
name = name,
image = ":" + name + ".image",
repo_tags = ["my.repo/py_{}:latest".format(name)] + repo_tags,
)
WORKSPACE
:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# -----------------------------------------------------------------------------
# Load rules_python repository - contains rules for building python code
# -----------------------------------------------------------------------------
http_archive(
name = "rules_python",
sha256 = "9acc0944c94adb23fba1c9988b48768b1bacc6583b52a2586895c5b7491e2e31",
strip_prefix = "rules_python-0.27.0",
url = "https://github.com/bazelbuild/rules_python/releases/download/0.27.0/rules_python-0.27.0.tar.gz",
)
load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
py_repositories()
python_register_toolchains(name = "python_runtime", python_version = "3.10")
load("@python_runtime//:defs.bzl", python_interpreter = "interpreter")
# -----------------------------------------------------------------------------
# Load rules_oci repository - contains rules for (docker) containers
# -----------------------------------------------------------------------------
http_archive(
name = "rules_oci",
sha256 = "4a276e9566c03491649eef63f27c2816cc222f41ccdebd97d2c5159e84917c3b",
strip_prefix = "rules_oci-1.7.4",
url = "https://github.com/bazel-contrib/rules_oci/releases/download/v1.7.4/rules_oci-v1.7.4.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)
load("@rules_oci//oci:pull.bzl", "oci_pull")
oci_pull(
name = "ubuntu_2204_base",
digest = "sha256:81bba8d1dde7fc1883b6e95cd46d6c9f4874374f2b360c8db82620b33f6b5ca1",
registry = "index.docker.io",
repository = "library/ubuntu",
)
# -----------------------------------------------------------------------------
# Load aspect_bazel_lib repository - useful for constructing rulesets and BUILD files
# -----------------------------------------------------------------------------
http_archive(
name = "aspect_bazel_lib",
sha256 = "f5ea76682b209cc0bd90d0f5a3b26d2f7a6a2885f0c5f615e72913f4805dbb0d",
strip_prefix = "bazel-lib-2.5.0",
url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.5.0/bazel-lib-v2.5.0.tar.gz",
)
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies", "aspect_bazel_lib_register_toolchains")
aspect_bazel_lib_dependencies() # Required bazel-lib dependencies
aspect_bazel_lib_register_toolchains() # Register bazel-lib toolchains