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 agemset.nix, e.g../gemset.nix. Unlike Poetry’s lock, aGemfile.lockdoes not carry content hashes, sogemset.nix(produced bybundixas an offline pre-step) is what supplies them. It must be a vendored path in your repo — never a read out of the fetchedsrc(that is import-from-derivation, forbidden by theno-ifd-lockfilelint).ruby(optional) — the interpreter (defaultruby_3_3, falling back toruby).gemdir(optional) — the directory holding theGemfile/Gemfile.lock(defaultsrc).
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.