Rules for creating container image layers from js_binary targets
For example, this js_image_layer target outputs node_modules.tar and app.tar with /app prefix.
load("@aspect_rules_js//js:defs.bzl", "js_image_layer") js_image_layer( name = "layers", binary = "//label/to:js_binary", root = "/app", )
Create container image layers from js_binary targets.
By design, js_image_layer doesn't have any preference over which rule assembles the container image. This means the downstream rule (oci_image from rules_oci or container_image from rules_docker) must set a proper workdir and cmd to for the container work.
A proper cmd usually looks like /[ js_image_layer 'root' ]/[ package name of js_image_layer 'binary' target ]/[ name of js_image_layer 'binary' target ], unless you have a custom launcher script that invokes the entry_point of the js_binary in a different path.
On the other hand, workdir has to be set to the “runfiles tree root” which would be exactly cmd but with .runfiles/[ name of the workspace ] suffix. If using bzlmod then name of the local workspace is always _main. If bzlmod is not enabled then the name of the local workspace, if not otherwise specified in the WORKSPACE file, is __main__. If workdir is not set correctly, some attributes such as chdir might not work properly.
js_image_layer creates up to 5 layers depending on what files are included in the runfiles of the provided binary target.
node layer contains the Node.js toolchainpackage_store_3p layer contains all 3p npm deps in the node_modules/.aspect_rules_js package storepackage_store_1p layer contains all 1p npm deps in the node_modules/.aspect_rules_js package storenode_modules layer contains all node_modules/* symlinks which point into the package storeapp layer contains all files that don't fall into any of the above layersIf no files are found in the runfiles of the binary target for one of the layers above, that layer is not generated. All generated layer tarballs are provided as DefaultInfo files.
The rules_js
node_modules/.aspect_rules_jspackage store follows the same pattern as the pnpmnode_modules/.pnpmvirtual store. For more information see https://pnpm.io/symlinked-node-modules-structure.
js_image_layer also provides an OutputGroupInfo with outputs for each of the layers above which can be used to reference an individual layer with using filegroup with output_group. For example,
js_image_layer( name = "layers", binary = ":bin", root = "/app", ) filegroup( name = "app_tar", srcs = [":layers"], output_group = "app", )
WARNING: The structure of the generated layers are not subject to semver guarantees and may change without a notice. However, it is guaranteed to work when all generated layers are provided together in the order specified above.
js_image_layer supports transitioning to specific platform to allow building multi-platform container images.
A partial example using rules_oci with transition to linux/amd64 platform.
load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_image_layer") load("@rules_oci//oci:defs.bzl", "oci_image") js_binary( name = "bin", entry_point = "main.js", ) platform( name = "amd64_linux", constraint_values = [ "@platforms//os:linux", "@platforms//cpu:x86_64", ], ) js_image_layer( name = "layers", binary = ":bin", platform = ":amd64_linux", root = "/app", ) oci_image( name = "image", cmd = ["/app/bin"], entrypoint = ["bash"], tars = [ ":layers" ], workdir = select({ "@aspect_bazel_lib//lib:bzlmod": "/app/bin.runfiles/_main", "//conditions:default": "/app/bin.runfiles/__main__", }), )
A partial example using rules_oci to create multi-platform images.
load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_image_layer") load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index") js_binary( name = "bin", entry_point = "main.js", ) [ platform( name = "linux_{}".format(arch), constraint_values = [ "@platforms//os:linux", "@platforms//cpu:{}".format(arch if arch != "amd64" else "x86_64"), ], ) js_image_layer( name = "{}_layers".format(arch), binary = ":bin", platform = ":linux_{arch}", root = "/app", ) oci_image( name = "{}_image".format(arch), cmd = ["/app/bin"], entrypoint = ["bash"], tars = [ ":{}_layers".format(arch) ], workdir = select({ "@aspect_bazel_lib//lib:bzlmod": "/app/bin.runfiles/_main", "//conditions:default": "/app/bin.runfiles/__main__", }), ) for arch in ["amd64", "arm64"] ] oci_image_index( name = "image", images = [ ":arm64_image", ":amd64_image" ] )
An example using legacy rules_docker
See e2e/js_image_docker for full example.
load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_image_layer") load("@io_bazel_rules_docker//container:container.bzl", "container_image") js_binary( name = "bin", data = [ "//:node_modules/args-parser", ], entry_point = "main.js", ) js_image_layer( name = "layers", binary = ":bin", root = "/app", visibility = ["//visibility:__pkg__"], ) filegroup( name = "node_tar", srcs = [":layers"], output_group = "node", ) container_layer( name = "node_layer", tars = [":node_tar"], ) filegroup( name = "package_store_3p_tar", srcs = [":layers"], output_group = "package_store_3p", ) container_layer( name = "package_store_3p_layer", tars = [":package_store_3p_tar"], ) filegroup( name = "package_store_1p_tar", srcs = [":layers"], output_group = "package_store_1p", ) container_layer( name = "package_store_1p_layer", tars = [":package_store_1p_tar"], ) filegroup( name = "node_modules_tar", srcs = [":layers"], output_group = "node_modules", ) container_layer( name = "node_modules_layer", tars = [":node_modules_tar"], ) filegroup( name = "app_tar", srcs = [":layers"], output_group = "app", ) container_layer( name = "app_layer", tars = [":app_tar"], ) container_image( name = "image", cmd = ["/app/bin"], entrypoint = ["bash"], layers = [ ":node_layer", ":package_store_3p_layer", ":package_store_1p_layer", ":node_modules_layer", ":app_layer", ], workdir = select({ "@aspect_bazel_lib//lib:bzlmod": "/app/bin.runfiles/_main", "//conditions:default": "/app/bin.runfiles/__main__", }), )
For better performance, it is recommended to split the large parts of a js_binary to have a separate layer.
The matching order for layer groups is as follows:
layer_groups are checked in order firstlayer_groups, the default layer groups are checked.The default layer groups are as follows and always created.
{
"node": "/js/private/node-patches/|/bin/nodejs/",
"package_store_1p": "\.aspect_rules_js/.*@0\.0\.0/node_modules",
"package_store_3p": "\.aspect_rules_js/.*/node_modules",
"node_modules": "/node_modules/",
"app": "", # empty means just match anything.
}
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| binary | Label to an js_binary target | Label | required | |
| compression | Compression algorithm. See https://github.com/bazel-contrib/bazel-lib/blob/bdc6ade0ba1ebe88d822bcdf4d4aaa2ce7e2cd37/lib/private/tar.bzl#L29-L39 | String | optional | "gzip" |
| directory_mode | Mode of the directories, in octal format. By default 0755 is used. | String | optional | "0755" |
| file_mode | Mode of the files, in octal format. By default 0555 is used. | String | optional | "0555" |
| generate_empty_layers | DEPRECATED. An empty layer is always generated if the layer group have no matching files. | Boolean | optional | False |
| layer_groups | Layer groups to create. These are utilized to categorize files into distinct layers, determined by their respective paths. The expected format for each entry is “”: “”, where MUST be a valid Bazel and JavaScript identifier (alphanumeric characters), and MAY be either an empty string (signifying a universal match) or a valid regular expression. | Dictionary: String -> String | optional | {} |
| owner | Owner of the entries, in GID:UID format. By default 0:0 (root, root) is used. | String | optional | "0:0" |
| platform | Platform to transition. | Label | optional | None |
| preserve_symlinks | Preserve symlinks for entries matching the pattern. By default symlinks within the node_modules is preserved. | String | optional | ".*/node_modules/.*" |
| root | Path where the files from js_binary will reside in. eg: /apps/app1 or /app | String | optional | "" |
PARAMETERS
| Name | Description | Default Value |
|---|---|---|
| ctx | - | none |