Bring your own derivation
Ecosystem detection covers Go, Rust, Python, and Ruby. When your project isn’t one of those — an unsupported ecosystem, a C/C++ build, a mixed build, or anything you’d rather assemble by hand — you skip detection entirely. There are two ways.
Option 1: mkHardenedImage directly
If you already have (or can build) the derivation, this is the simplest path.
mkHardenedImage wraps an already-built derivation into a hardened image and
attaches its SBOM. It does no detection at all.
let
myThing = pkgs.stdenv.mkDerivation {
pname = "my-thing";
version = "1.0.0";
src = ./.;
nativeBuildInputs = [ pkgs.cmake ];
# …your build…
};
in
hb.mkHardenedImage {
name = "my-thing";
version = "1.0.0";
package = myThing;
cmd = [ "/bin/my-thing" ];
}
This is the headline entry point — mkSecureArtifact funnels into it once it
has a derivation. If you’re not using the ecosystem dispatch, prefer this over
mkSecureArtifact; there’s no reason to route through detection you don’t want.
Option 2: mkSecureArtifact with overrideDerivation
If you’re inside a pipeline that’s already shaped around mkSecureArtifact and
you want to keep that shape but bypass detect-and-build for one artifact, pass
overrideDerivation. It short-circuits detection and building entirely and
images the derivation you hand it:
hb.mkSecureArtifact {
name = "my-thing";
version = "1.0.0";
overrideDerivation = myThing; # skip detect + build, image THIS
image.cmd = [ "/bin/my-thing" ];
}
When you use overrideDerivation, you don’t need src — the throw that
mkSecureArtifact raises for an undetectable src doesn’t fire, because
detection is skipped. The result’s passthru.ecosystem is
{ ecosystem = "custom"; }, a marker that this artifact came from an override
rather than a detected ecosystem.
Which one?
They produce the same image and SBOM. Use mkHardenedImage when you’re building
images directly, and overrideDerivation only when you want a single uniform
mkSecureArtifact call site across a mix of detected and custom artifacts.
Everything else still applies
However you produce the derivation, the rest is unchanged: the CI component
signs, attests, and scans it exactly the same way (see
Integrate an external project), the SBOM covers its
Nix closure (see SBOMs), and fips = true remains a truthfulness
contract you must back with a real FIPS build (see FIPS).