Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ruby

mkSecureArtifact detects a Ruby project by a Gemfile.lock at the root of src. It builds a bundlerEnv from an enriched gemset and hands the result to mkHardenedImage.

The marker

The primary marker is Gemfile.lock. A Gemfile alongside it is recorded as the buildConfigFile.

Required and optional ecosystemArgs

  • gemset (required) — a vendored relative path to a gemset.nix, e.g. ./gemset.nix. Unlike Poetry’s lock, a Gemfile.lock does not carry content hashes, so gemset.nix (produced by bundix as an offline pre-step) is what supplies them. It must be a vendored path in your repo — never a read out of the fetched src (that is import-from-derivation, forbidden by the no-ifd-lockfile lint).
  • ruby (optional) — the interpreter (default ruby_3_3, falling back to ruby).
  • gemdir (optional) — the directory holding the Gemfile/Gemfile.lock (default src).

Worked example

Generate gemset.nix with bundix, vendor it into your repo, then:

hb.mkSecureArtifact {
  name = "my-rails-app";
  version = "3.1.0";
  src = ./.;
  ecosystemArgs = {
    gemset = ./gemset.nix; # vendored, relative
    ruby = pkgs.ruby_3_3;
  };
  image.cmd = [ "/bin/rails" "server" ];
}

The emitter imports the gemset and runs it through enrichGemset, which substitutes gems already built in nixpkgs for their pre-built store paths so bundlerEnv only source-fetches the gems nixpkgs doesn’t carry. The default match mode is exact (substitute only on an exact version match), which keeps Bundler’s strict lockfile-version enforcement happy. enrichGemset also guards against a substitution dragging a conflicting version of a monkey-patched gem (e.g. net-http) onto GEM_PATH.

Derivation-first alternative

If you need finer control over the bundlerEnv (custom matchMode, extra nativeBuildInputs, explicit nixpkgsGems), build it yourself and image it:

let
  gems = pkgs.bundlerEnv {
    name = "my-rails-app-gems";
    ruby = pkgs.ruby_3_3;
    gemdir = ./.;
  };
  app = pkgs.stdenv.mkDerivation {
    pname = "my-rails-app";
    version = "3.1.0";
    src = ./.;
    nativeBuildInputs = [ gems gems.wrappedRuby ];
    # …install phase…
  };
in
hb.mkHardenedImage {
  name = "my-rails-app";
  version = "3.1.0";
  package = app;
  cmd = [ "/bin/rails" "server" ];
}

SBOM

Ruby gems cannot be inferred from the built derivation the way Go modules or Rust crates can — their inventory lives in the gemset.nix. So the SBOM builder needs you to point at the vendored gemset explicitly with sbomEcosystems:

hb.mkSecureArtifact {
  name = "my-rails-app";
  version = "3.1.0";
  src = ./.;
  ecosystemArgs.gemset = ./gemset.nix;
  sbomEcosystems = [ { kind = "ruby"; gemset = ./gemset.nix; } ];
}

That vendored-path rule is the same no-IFD discipline as everywhere else — see SBOMs.