如何在NixOS Docker镜像中配置可调用的Nix包(如file工具)
在NixOS Docker镜像中集成file工具并让可执行文件调用它的解决方案
咱们先拆解一下你遇到的问题:你已经用Nix的dockerTools构建了镜像,把预编译的foobar放进去了,但它调用file工具识别MIME类型时失败,之前用buildInputs的方式没生效。本质原因是**buildInputs主要是给构建阶段提供依赖的,而不是运行时**——你得确保file工具和它的依赖在镜像运行时能被foobar找到才行。
我给你两个可行的修复方案,你可以根据自己的需求选:
方案一:用makeWrapper包装可执行文件(推荐)
这个方法最干净,直接把foobar需要的运行时环境(比如file的路径、依赖库)绑定到可执行文件本身,不需要在entrypoint里额外配置。修改你的docker.nix如下:
with import <nixpkgs> {}; let foobar_deriv = stdenv.mkDerivation rec { name = "foobar"; builder = "${bash}/bin/bash"; args = [ ./nix-builder.sh ]; inherit coreutils openssl libyaml; system = builtins.currentSystem; schemapath = ../../schemas; foobarpath = ./foobar; # 加入makeWrapper用来包装可执行文件,保留buildInputs供构建使用 buildInputs = [ pkgs.bash pkgs.file pkgs.makeWrapper ]; installPhase = '' # 先把你的foobar可执行文件安装到输出目录 mkdir -p $out/bin cp $foobarpath $out/bin/foobar chmod +x $out/bin/foobar # 用wrapProgram给foobar设置运行时环境: # 1. 把file的bin目录加到PATH,让foobar能找到file命令 # 2. 把所有需要的依赖库路径加到LD_LIBRARY_PATH wrapProgram $out/bin/foobar \ --prefix PATH : "${pkgs.file}/bin" \ --prefix LD_LIBRARY_PATH : "${stdenv.lib.makeLibraryPath [ pkgs.openssl pkgs.libyaml pkgs.file ]}" ''; }; entrypoint = writeScript "entrypoint.sh" '' #!${stdenv.shell} exec $@ ''; in pkgs.dockerTools.buildImage { name = "myaccount/foobar"; tag = "0.3.0a11"; created = "now"; contents = foobar_deriv; config = { Cmd = [ "foobar" ]; Entrypoint = [ entrypoint ]; ExposedPorts = { "4949/tcp" = {}; }; WorkingDir = "/"; }; }
关键修改点:
- 新增了
pkgs.makeWrapper到buildInputs,这是Nix用来包装可执行文件、注入运行时环境的工具 - 添加了
installPhase,手动处理foobar的安装,并通过wrapProgram配置它的运行时PATH(包含file的二进制路径)和LD_LIBRARY_PATH(包含所有依赖库,包括file的依赖) - 去掉了之前的
envbuildEnv,因为wrapProgram已经帮我们搞定了运行时依赖路径的问题
方案二:直接把file工具加入镜像contents
如果你不想修改foobar的derivation,可以直接把file工具加到镜像的contents里,然后在entrypoint里配置环境变量让foobar能找到它:
with import <nixpkgs> {}; let foobar_deriv = stdenv.mkDerivation rec { name = "foobar"; builder = "${bash}/bin/bash"; args = [ ./nix-builder.sh ]; inherit coreutils openssl libyaml; system = builtins.currentSystem; schemapath = ../../schemas; foobarpath = ./foobar; buildInputs = [ pkgs.bash pkgs.file ]; env = buildEnv { name = name; paths = buildInputs; }; }; # 把file的库路径也加入LD_LIBRARY_PATH ld_path = stdenv.lib.makeLibraryPath [ pkgs.openssl pkgs.libyaml pkgs.file ]; entrypoint = writeScript "entrypoint.sh" '' #!${stdenv.shell} export LD_LIBRARY_PATH=${ld_path} # 把file的bin目录加到系统PATH export PATH="${pkgs.file}/bin:$PATH" exec $@ ''; in pkgs.dockerTools.buildImage { name = "myaccount/foobar"; tag = "0.3.0a11"; created = "now"; # 把file工具直接加入镜像contents contents = [ foobar_deriv pkgs.file ]; config = { Cmd = [ "foobar" ]; Entrypoint = [ entrypoint ]; ExposedPorts = { "4949/tcp" = {}; }; WorkingDir = "/"; }; }
这个方法的核心是:
- 把
pkgs.file加入contents,让镜像里存在file工具 - 在entrypoint里把
file的bin目录加到PATH,同时把file的库路径加到LD_LIBRARY_PATH
两种方案都能解决你的问题,推荐第一种,因为它把依赖和可执行文件绑定在一起,后续维护更省心。
内容的提问来源于stack exchange,提问作者James Fleming




