A More Complicated Docker Image for Rust with Nix
After my successful integration of Nix with a trivial Rust
application, I tried it with one that uses the opencv
crate. I first added this to my buildRustPackage
call:
NixbuildInputs = [
rustPlatform.bindgenHook
rustPlatform.cargoSetupHook
opencv
];
nativeBuildInputs = [
pkgs.pkg-config
pkgs.clang
];
LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
But it failed even with a completely empty test application (created with cargo init and nothing more), ultimately showing this:
clippy-preview> auto-patchelf: 0 dependencies could not be satisfied test> unpacking sources test> unpacking source archive /nix/store/z8sc7a2cm3mhv6wc51vvhqkqwxdk2kjf-2x67xy58ghamzwhv4083n43lfzrf5zx2-source test> source root is 2x67xy58ghamzwhv4083n43lfzrf5zx2-source test> Executing cargoSetupPostUnpackHook test> unpacking source archive /nix/store/mbak1ga8nj98z11m27ygbx09h45z5ad5-cargo-vendor-dir test> Finished cargoSetupPostUnpackHook test> Executing cargoSetupPostUnpackHook test> unpacking source archive /nix/store/mbak1ga8nj98z11m27ygbx09h45z5ad5-cargo-vendor-dir test> cp: cannot create directory 'cargo-vendor-dir/mbak1ga8nj98z11m27ygbx09h45z5ad5-cargo-vendor-dir': Permission denied test> do not know how to unpack source archive /nix/store/mbak1ga8nj98z11m27ygbx09h45z5ad5-cargo-vendor-dir error: builder for '/nix/store/rrs8wlwi8pzr6lxjfg67xh98j4npv4qr-test-0.1.0.drv' failed with exit code 1 error: 1 dependencies of derivation '/nix/store/f1bkzblxvp8xmaxi0px9417g5ysb0qf5-test-config.json.drv' failed to build error: 1 dependencies of derivation '/nix/store/183k1sb6yw1rzmwb4h85cz29m9lvrs0s-docker-image-test.tar.gz.drv' failed to build
I got around that by removing cargoSetupHook
, giving me:
NixbuildInputs = [
rustPlatform.bindgenHook
opencv
];
That made it fail with a permission denied error after generating the headers, which is a known issue with the crate under Nix. I hope it’s fixed soon, because the application would really benefit from Nix’s precision.
I wanted to try building just that crate with OUT_DIR
set. cargoDeps
sounded right, if I used
buildRustPackage
to build rust-opencv separately then add it to the dependencies, but all the uses
I see in the nixpkgs repository show cargoDeps
being set to the result of fetching Cargo packages
or tarballs in special ways, so I don’t know how to do it. crateOverrides
looked promising but
it’s for buildRustCrate
, which builds packages without Cargo, so I guess I’d have to switch to
that to use it.
I decided to try nocargo, which also builds crates without
Cargo but provides a clear way to customize dependencies. It
failed at first because of a hardcoded -C embed-bitcode=no
,
which is incompatible with link-time optimization
(LTO):
rust_adler> unpacking sources rust_adler> unpacking source archive /nix/store/lhwj837snajf713x99vys2yk4mcrg1im-crate-adler-1.0.2.tar.gz rust_adler> source root is adler-1.0.2 rust_adler> patching sources rust_adler> configuring rust_adler> building rust_adler> Building lib: RUSTC 'src/lib.rs' '--out-dir=/nix/store/nfmmndz97720f1xgkl4rsy41y81qdfly-rust_adler-1.0.2/lib' '--crate-name=adler' '--crate-type=lib' '--emit=metadata,link' '-Cembed-bitcode=no' '-Cextra-filename=-11fdd89355321299' '--color=always' '-Copt-level=3' '-Cdebuginfo=0' '-Cdebug-assertions=no' '-Coverflow-checks=no' '-Clto=thin' '-Cpanic=unwind' '-Ccodegen-units=1' '--cap-lints=allow' '-Cmetadata=11fdd89355321299' '-Ldependency=/nix/store/z03r6pvjz02aac1s9ivvx4x403741137-rust_adler-1.0.2-dev/rust-support/deps-closure' rust_adler> error: options `-C embed-bitcode=no` and `-C lto` are incompatible
I disabled LTO and liberally sprinkled nativeBuildInputs
around various parts of the flake
definition. In the end, my outputs looked like this:
Nixoutputs = { nixpkgs, flake-utils, nocargo, ... }@inputs:
flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
let
pkgs = import nixpkgs { inherit system; config.allowUnfree = true; };
opencv = pkgs.opencv.override (old : {
enableEXR = false;
enableGStreamer = false;
enableUnfree = true;
enableTIFF = false;
});
ws = nocargo.lib.${system}.mkRustPackageOrWorkspace {
src = ./.;
buildCrateOverrides = with nixpkgs.legacyPackages.${system}; {
"clang-sys 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = old: {
LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
nativeBuildInputs = [ pkgs.pkg-config pkgs.llvm pkgs.clang ];
};
"opencv 0.65.0 (registry+https://github.com/rust-lang/crates.io-index)" = old: {
OUT_DIR = ".";
nativeBuildInputs = [ opencv pkgs.pkg-config pkgs.llvm pkgs.clang ];
};
# The crate in the workspace that needs OpenCV.
"crate-needing-opencv" = old: {
nativeBuildInputs = [ opencv ];
};
};
};
in rec {
packages = ws.release
// nixpkgs.lib.mapAttrs' (name: value: { name = "${name}-dev"; inherit value; }) ws.dev;
dockerImage = pkgs.dockerTools.buildLayeredImage {
name = "app-name";
config = { Entrypoint = [ "${packages.rest.bin}/bin/crate-needing-opencv" ]; };
};
defaultPackage = dockerImage;
});
Sadly, after all that effort, the final image is much larger—780 MB, compared to 198 MB for the Dockerfile-derived version—despite the theoretical precision of Nix. This is probably because it desperately needs that LTO step. I opened an issue in the nocargo repository and I’m exploring possible solutions. For the moment, I’ll continue using the Dockerfile.