NativeLink
Reference

CLI and environment

Every command-line argument the nativelink binary accepts and every environment variable it reads: the telemetry variables, the ones the configuration expands, and the ones it passes through to actions.

Who this is for: you are writing a systemd unit, a Dockerfile CMD, or a Kubernetes env: block and need to know what the process actually reads. What you'll have at the end: the complete argument and variable surface, with the sharp edges marked.

NativeLink's command line is deliberately tiny. Almost every decision lives in the JSON5 configuration file, not in flags, which means the interesting surface here is the environment, split across three unrelated jobs: configuring telemetry, filling in holes in the configuration file, and reaching the processes that actions run in.

The command line

Args
nativelink <config_file>

That is the whole thing. config_file is a required positional argument, the path to a JSON5 configuration file, and there are no other arguments. The binary is a clap parser with version, about and author derived, so --help and --version work and nothing else does.

ArgumentRequiredMeaning
<config_file>YesPath to the JSON5 configuration file. Parsed with serde_json5; the config structs are deny_unknown_fields, so an unrecognised key is a startup failure, not a warning.
--helpNoPrint usage and exit.
--versionNoPrint the crate version and exit.

No --port, no --log-level, no --config-from-env exist. If you are looking for a knob, it is in the configuration file; see the configuration reference.

Telemetry variables

These are read once, at startup, by nativelink-util's tracing initialiser. They are the only variables that change the binary's own behaviour.

init_tracing
VariableDefaultEffect
NL_LOGprettyFormat of the stdout log layer. compact and json are recognised; any other value silently falls back to pretty, including a typo like Json.
RUST_LOGinfoLog filter, parsed by tracing_subscriber's EnvFilter in lossy mode; an unparseable directive is dropped rather than fatal. Applies to the stdout layer and to the OTLP log, trace and metric layers. hyper, tonic, h2, reqwest and tower are forced off afterwards and cannot be re-enabled from here.
NL_OTEL_ENDPOINTnot setWhen set, all three OTLP exporters share one client-side load-balanced gRPC channel pointed at this endpoint. When unset, each exporter is built with the SDK's own tonic defaults instead.
OTEL_EXPORTER_OTLP_ENDPOINThttp://localhost:4317Standard OpenTelemetry SDK variable. Applies only when NL_OTEL_ENDPOINT is unset; an explicit channel wins over the SDK's endpoint resolution. The signal-specific OTEL_EXPORTER_OTLP_{LOGS,TRACES,METRICS}_ENDPOINT variables behave the same way.
OTEL_METRIC_EXPORT_INTERVAL60000 (ms)How often the periodic metric reader flushes to the collector. NativeLink sets no interval of its own, so the SDK default of one minute applies unless you override it here. This is the first hop in the reaction-time budget.

The service name is fixed. Resource::builder().with_service_name("nativelink") is called unconditionally, so OTEL_SERVICE_NAME does not change what appears on your spans. service.instance.id is a fresh UUIDv4 generated per process, which means it changes on every restart: useful for telling replicas apart, useless as a stable series identity.

Variables the configuration file expands

Many configuration fields are deserialized through a shellexpand wrapper rather than directly, so their string values undergo environment substitution before the value is used.

convert_string_with_shellexpand

Two forms are available:

FormBehaviour
${VAR} or $VARSubstitutes the variable. An unset variable is an error, and the error surfaces as a config parse failure.
${VAR:-default}Substitutes the variable, or the literal default when it is unset. A variable that is set but empty expands to the empty string, not to the default.

This is what makes one configuration file work across environments:

{
  stores: [
    {
      name: "CAS_MAIN_STORE",
      memory: {
        eviction_policy: {
          max_bytes: "${NATIVELINK_CAS_MEMORY_CONTENT_LIMIT:-100mb}",
        },
      },
    },
  ],
}

Not every field is expanded; the wrapper is applied per field, so whether a given key supports substitution depends on how its struct member is annotated. Numeric, boolean, duration and data-size fields have their own expanding deserializers, which is why max_bytes above accepts the string "100mb" rather than a number. When in doubt, the configuration reference records the type each field actually deserializes from.

Expansion happens at parse time, not at use time

The substitution runs once, while the configuration file is being read at startup. Changing a variable in the environment of a running process has no effect until it restarts.

Variables the worker passes to actions

A worker does not forward its own environment to the commands it runs: the child is spawned with env_clear(). An action sees exactly two things: the environment_variables its own Command message carries, and whatever the worker's additional_environment map (on the local worker configuration) names explicitly.

EnvironmentSource
SourceWhat the action receives
ValueA literal string, itself shellexpanded at config parse time, so Value is one way to deliberately pass a worker-side variable through.
FromEnvironmentThe worker process's value for the variable of the same name (empty if unset). This is the only runtime pass-through, and it is opt-in per variable.
PropertyThe value of the named platform property from the action, or the empty string if the action did not set it.
TimeoutMillisThe timeout the worker will enforce, in milliseconds: the client-requested timeout, or max_action_timeout when the client asked for none.
SideChannelFileA path (a fresh UUID under the action directory) the action may write JSON to in order to influence how its result is interpreted; {"failure": "timeout"} marks the action as timed out.
ActionDirectoryA scratch directory that is purged after the action completes. The usual pattern is an entrypoint script that creates a tmp subdirectory under it and exports TMPDIR.

Stated explicitly, because each of these is a reasonable guess that is wrong:

  • No environment-based configuration. No NL_CAS_PORT or equivalent. Ports, stores, schedulers and services come from the configuration file only. The escape hatch is ${VAR} substitution inside that file.
  • No NATIVELINK_CONFIG variable. The configuration path is the positional argument and nothing else.
  • No Prometheus scrape configuration. The binary exports OTLP and only OTLP; the namespace, const_labels and scrape endpoint you may be looking for belong to the OpenTelemetry collector's configuration, not NativeLink's. See the metrics reference.
  • No OTEL_SERVICE_NAME honouring, as above; the service name is a compile-time constant.

Reading the source

If anything here disagrees with the binary, the source wins:

Where to go next

NextConfiguration reference

Everything the command line does not do, which is nearly everything, generated from the config crate itself.

SidewaysWire up telemetry

The variables above in the context of a working collector pipeline.

SidewaysOpen source and Enterprise

Why there is no licence flag here, and what is licensed differently anyway.

On this page