What is the nix package if I want to pull nixpkgs into my derivation?

More questions about embedded nix into my docker image: What do I need to do to get nixpkgs into my nix derivation, so that import <nixpkgs> works correctly?

1 Like

<nixpkgs> references the NIX_PATH

$ echo $NIX_PATH
/home/jon//.nix-defexpr/channels:nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels
1 Like

Ok… so can you spell it out for me a bit more: How do I access the derivation that contains nixpkgs?

Nix uses nixpkgs as a “package/derivation respository”. What you want is to be able to reference it.

For nix usage: https://www.youtube.com/watch?v=LiEqN8r-BRw&t

For pinning nixpkgs: FAQ/Pinning Nixpkgs - NixOS Wiki

1 Like

Not quite - I want it to be copied into my docker image alongside the other packages that I plan to include.

I think it would be really helpful if you could take a few sentences to describe the overall goal. It feels like there could be some XY Problem going on. https://xyproblem.info/

2 Likes

If your derivation refers to nixpkgs it will have an automatic dependency, so setting the NIX_PATH environment variable in the container to “${nixpks}” should be enough. Are you using dockerTools.buildImage?

Quite right. So I am building a docker image for a Continious Integration system for a piece of ARM firmware. Making an image with these capabilities is quite simple i.e. a simple image with GNU Make, ARM cross-gcc etc. embedded.

But when the Build Server runs the docker image, what shell environment will it run so that everything executes, includes and links as expected?

For example, as part of the build process of that piece of ARM firmware, there is a post-processing step for which a C-language tool is compiled for the host machine using gcc, and linking in OpenSSL. So we need an environment where gcc -o tool tool.c -lcrypto works as expected, and the executable can then run.

However, this doesn’t work if we just run bash, because the environment variables will not be populated to enable gcc to find libcrypto or its headers.

Furthmore, if the build fails on the build server, I want developers to be able to pull down the docker image and easily run a Nix shell where they can run things manually to figure out what went wrong.

I’m finding these last steps surprisingly difficult to achieve with Nix. It seems that Nix dockerTools was not really designed for this use-case - or that noone tried to do this before? …which surprises me, because deterministic build environments seems like the perfect use-case for Nix.

Thinking about it… I pulled in pkgs.nix, but there are still extra steps to set up the system for nix e.g. create /tmp etc., and here I am asking about how to include nixpks - perhaps a simpler approach would be to base my docker image on top of the nixos/nix which would have all this stuff set up already.

Yeah, that’s probably what you want.

Alternatively you could make your own with https://github.com/nix-community/nixos-generators, though because nixpkgs uses systemd for everything making a small container image is very impractical, compared to dockerTools, but this one gives you more control over contents.

Pulling nixpkgs into your docker image will do absolutely nothing for you. You’d still need to give them network access - it’s just package definitions, not a set of prebuilt binaries.

If you want your users to be able to run nix inside the image then put the nix package in the image and set up its channels.

I’m also unsure how having nixpkgs in the image is supposed to fix your environment problems. Do you believe having nixpkgs in the image will let you configure nixos modules? If so, it doesn’t, you’re looking for nixos-generators.

Also be aware that things compiled in a nix env won’t trivially run in a normal fhs env, because it will link things into /nix/store. This can be solved, but you might need to PoC the deployment before you run with this concept.

This is all indeed not really the intended path for nix. Usually you build with nix on the host, since it makes containers for dependency management practically unnecessary. Personally build envs in docker is a major pet peeve of mine in general, it always makes IDEs so much harder to use, and debugging a little more tricky. But organizations do insist on them, usually easier than getting a piece of software approved for installation…

1 Like

You can access the location of an imported nixpkgs via the path attribute, e.g.:

let
  pkgs = import <nixpkgs> {};
in
pkgs.path
# on my system would evaluate to /nix/var/nix/profiles/per-user/root/channels/vuizvui/nixpkgs

You can use this along with string interpolation to get it copied into the docker image, e.g.

pkgs.writeText "envfile" ''
  NIX_PATH=nixpkgs=${pkgs.path}
''

Would build a file that references the nixpkgs tree you used for creating it – imported into the store. I’ve never actually used dockerTools actively, so I’ll leave it to you to figure out how to best set an environment variable in there.

1 Like

You’d create an entrypoint script with pkgs.writeShellScriptBin, and then set it as the Entrypoint in the config attr of your dockertools invocation, something like this:

let
  entryPoint = pkgs.writeShellScriptBin "entrypoint.sh" ''
    export NIX_PATH=nixpkgs=${pkgs.path}
    exec $@
  '';
in buildImage {
  copyToRoot = pkgs.buildEnv {
    name = "image-root";
    paths = [ entryPoint ];
    pathsToLink = [ "/bin" ]; # Can link full packages with "/" too
  };

  config = {
    Entrypoint = [ "/bin/entrypoint.sh" ];
  };
}

Docker will add whatever you put in Cmd, or what the user types at the command line when they execute docker run, to the end of that, so the entrypoint will be executed, export that path and then exec whatever Cmd is set to - this is generally how OCI image entrypoints work, and usually results in the expected behavior.

For reference, writeShellScriptBin will use runtimeShell, so this sets up bash by default, which will also be put in the container. If you’d rather use something like busybox, you will probably want to change that.

You could also use the OCI Env config and avoid the bloat, but I’m pretty sure this will not actually add the contents to the image.

Again though, by itself this won’t solve the problems described here; you still need a network connection to download packages, and this doesn’t enable configuring nixos modules in the image.

You can manually set gcc’s env variables through an EntryPoint script, but I think the intention is to use nixos’ module system for that?

1 Like

Just experimenting today. I tried three different approaches, though only the first is an actual working solution:


{ pkgs ? import <nixpkgs> { } }:
{
  # Approach 1: Set GCC search path environment variables
  # Output Size: 114Mb
  # Pro: Simple
  # Pro: Lightweird
  # Con: Nasty bash default terminal configuration
  # Con: Manual include path variables feels inelegant
  dockerEnvPaths = let
    libs = [ pkgs.openssl ];
  in pkgs.dockerTools.buildLayeredImage {
    name = "test_env_paths";
    tag = "latest";
    contents = [
      pkgs.bashInteractive
      pkgs.gcc
      pkgs.coreutils
    ];
    config.Env = [
      ("C_INCLUDE_PATH=" + (builtins.concatStringsSep ":" (builtins.map (l: pkgs.lib.getDev l + "/include") libs)))
      ("LIBRARY_PATH=" + (builtins.concatStringsSep ":" (builtins.map (l: pkgs.lib.getLib l + "/lib") libs)))
    ];
  };

  # Approach 2: Build on top of nixos/nix public docker image
  # Input Image Size: 593Mb
  # Fails with weird error in nix-build:
  #   FATA[0000] initializing source docker://nixos/nix@sha256:1d13ae379fb8caf3f859c5ce7ec6002643d60cf8b7b6147b949cc34880c93bac: getting username
  #    and password: 1 error occurred:
  #           * reading JSON file "/run/containers/997/auth.json": open /run/containers/997/auth.json: permission denied
  dockerNixOSBase = let
    nixFromDockerHub = pkgs.dockerTools.pullImage {
      imageName = "nixos/nix";
      imageDigest = "sha256:1d13ae379fb8caf3f859c5ce7ec6002643d60cf8b7b6147b949cc34880c93bac";
      sha256 = "0pwlmk5xzqrp753wg2dn2jnl3jsad22ghq29mlpqx25dsprfnhdc";
      finalImageTag = "2.8.0";
      finalImageName = "nix";
    };
  in pkgs.dockerTools.buildLayeredImage {
    name = "test_nixos_base";
    tag = "latest";
    fromImage = nixFromDockerHub;
  };

  # Approach 3: Pull in Nix build environment
  # Output Size: 141Mb
  dockerImportNix = let
  # nix-shell fails with error:
  #   error: cannot figure out user name
  in pkgs.dockerTools.buildImage {
    name = "test_import_nix";
    tag = "latest";
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      paths = [
        (pkgs.lib.getDev pkgs.openssl)
        (pkgs.lib.getLib pkgs.openssl)
        pkgs.bash
        pkgs.coreutils
        pkgs.gcc
        pkgs.nix
      ];
      pathsToLink = [ "/bin" ];
    };

    config = {
      Env = [
        "NIX_PATH=nixpkgs=${pkgs.path}"
      ];
    };
  };
}

Manully constructing the GCC search paths isn’t too bad - it just feels like this is something nix should have a mechanism to do automatically.

Here is some test code test.c:

#include <openssl/crypto.h>

int main(void) {
  printf("OpenSSL version: %s\n", OpenSSL_version(OPENSSL_VERSION));
  return 0;
}

For the environment to be well-formed it should be possible to make a running executable using the command:

gcc -o test test.c -lcrypto

Obviously this executable won’t be portable outside the docker environment, but because it’s a build tool, it doesn’t matter.

It looks like this is exactly what you need: https://github.com/NixOS/nixpkgs/pull/172736

Yes indeed - that looks very close to my needs. I wonder if it will get merged.

If you want that merged, please test it and give some feedback, that will help