Rules systemd

Executable Bazel targets with user systemd resource limits

rules_systemd is a standalone Linux Bazel module. Add a bazel_dep on rules_systemd and load its public systemd/defs.bzl API. The module uses its own dependency manifest and lockfile; the parent checkout supplies a local module override for development.

The systemd_run rule exposes a normal executable Bazel target with its resource policy declared in BUILD.bazel. It runs the application through the host’s systemd-run --user --scope, preserving literal arguments, environment, working directory, standard streams, signals, and exit status. It does not install services or change persistent host configuration.

load("@rules_systemd//systemd:defs.bzl", "systemd_run")

systemd_run(
    name = "server",
    binary = ":server_bin",
    arguments = ["--input", "$(rlocationpath :input.txt)"],
    data = ["input.txt"],
    properties = {
        "MemoryHigh": "96M",
        "MemoryMax": "128M",
        "MemorySwapMax": "0",
    },
)

Run it with bazel run //path/to:server. Arguments after -- are appended to arguments. Configure the application’s default arguments on the wrapper; defaults from the raw binary’s Bazel args attribute are not inherited. arguments supports location/runfiles expressions referring to binary and data. The rule merges their runfiles and preserves the binary’s RunEnvironmentInfo.

The rule uses the existing hermetic copy action to package the Go launcher and an adjacent generated JSON configuration. The launcher resolves the application through Bazel runfiles. Both output files are required when packaging the executable. No shell launch script or per-target Go build is needed. All systemd execution occurs at runtime, outside build actions.

The host systemd must support --expand-environment=no; the launcher uses it to preserve dollar expressions literally in child arguments.

Properties use systemd’s native names and values; at least one is required. Unsupported properties, unavailable controllers, or an unavailable user systemd manager fail without an unbounded fallback. The command runs in the foreground and Ctrl+C stops it. Ordinary Bazel run-lock behavior remains; Bazel’s persistent server stays outside the new scope.

The CLI remains available for direct commands:

bazel run @rules_systemd//:systemd_run -- \
  --property=MemoryMax=128M --property=MemorySwapMax=0 -- /usr/bin/true

ComfyUI declares its existing model limits through this rule. The small //test:probe fixture writes JSON evidence of real kernel limits, argument forwarding, environment, and runfiles when supplied -- --report <absolute-output-path>.

Development

Run bazel build //... and bazel test //... from this module directory. //:build_test checks the public rule against the runfiles fixture. Runtime verification requires a Linux user systemd manager and delegated memory controllers; invoke //test:probe with an absolute --report path to record actual kernel limits.