Pull Docker image for later use

I am in the process of converting custom Linux images from an Ansible build pipeline to NixOS. The image is used during programming contests, so the general idea is to build the image, flash a bunch of USB drives, and boot these to end up in a ready-to-use and fully set-up Linux environment.

One thing we do in our original Ansible environment is pull a Docker image during build, so that when we need to deploy the containers during the contest, Docker doesn’t first need to pull the image anymore. This is a huge timesaver. (How) can I achieve this on NixOS? There seems to be a pkgs.dockerTools.pullImage thing, but it seems like this is only for building something on top of it afterwards? I just want to store the image locally :).

Thanks!

You can try something like this

  environment.systemPackages = [
    (pkgs.writeShellScriptBin "preload-images" ''
      # nix run nixpkgs#nix-prefetch-docker -- --image-name debian --image-tag buster
      docker load -i ${pkgs.dockerTools.pullImage {
        imageName = "debian";
        imageDigest = "sha256:58ce6f1271ae1c8a2006ff7d3e54e9874d839f573d8009c20154ad0f2fb0a225";
        sha256 = "1gybjys977mr4108bzkwhfb03qrrl6fxgr6jy67k3p1bx7s4jxwf";
        finalImageName = "debian";
        finalImageTag = "buster";
      }}
    '')
  ];

You then should be able to run the preload-images command to load debian:buster image to the local docker daemon. Or you can call it in a oneshot systemd service

1 Like

Awesome, thanks!

If I’m understanding this correctly, this is basically like writing a custom shell script? Is the preload-images then something that is available in the PATH or how does that work?

Additionally, how do I run the script during build so that even on first boot, the image is already present?

Lastly, what does the comment represent?

Sorry for the many questions, but I’m learning a lot these last few weeks :wink:

The pkgs.writeShellScriptBin makes a shell script, yes.

If you add it to the environment.systemPackages then yes, it will end up in the PATH

The image will be a part of the preload-images script. The script will look like this:

docker load -i /nix/store/6yb86hqs3gdi0rcyfxrqw0qq9z4qvx6q-docker-image-debian-buster.tar

It’s up to you when to execute this script.

This command will spit out the nix code you need to pass to the pkgs.dockerTools.pullImage function.

% nix run nixpkgs#nix-prefetch-docker -- --image-name debian --image-tag buster
Getting image source signatures
Copying blob 3892befd2c3f done   |
Copying config 69530eaa9e done   |
Writing manifest to image destination
-> ImageName: debian
-> ImageDigest: sha256:58ce6f1271ae1c8a2006ff7d3e54e9874d839f573d8009c20154ad0f2fb0a225
-> FinalImageName: debian
-> FinalImageTag: buster
-> ImagePath: /nix/store/6yb86hqs3gdi0rcyfxrqw0qq9z4qvx6q-docker-image-debian-buster.tar
-> ImageHash: 1gybjys977mr4108bzkwhfb03qrrl6fxgr6jy67k3p1bx7s4jxwf
{
  imageName = "debian";
  imageDigest = "sha256:58ce6f1271ae1c8a2006ff7d3e54e9874d839f573d8009c20154ad0f2fb0a225";
  sha256 = "1gybjys977mr4108bzkwhfb03qrrl6fxgr6jy67k3p1bx7s4jxwf";
  finalImageName = "debian";
  finalImageTag = "buster";
}

Thanks for the detailed answer! I really appreciate the help.

Would it then also be an option to define this as a normal Nix function and execute it as an activation script in home-manager? That way it would run during evaluation?

Not sure what you mean by “function”.

It’s better to just use a system systemd service. This should do the trick (didn’t test):

{ config, pkgs, ... }:
{
  systemd.services.docker-preload = {
    after = [ "docker.service" ];
    requires = [ "docker.service" ];
    wantedBy = [ "multi-user.target" ];
    path = [ config.virtualisation.docker.package ];
    script = ''
      docker load -i ${pkgs.dockerTools.pullImage {
        imageName = "debian";
        imageDigest = "sha256:58ce6f1271ae1c8a2006ff7d3e54e9874d839f573d8009c20154ad0f2fb0a225";
        sha256 = "1gybjys977mr4108bzkwhfb03qrrl6fxgr6jy67k3p1bx7s4jxwf";
        finalImageName = "debian";
        finalImageTag = "buster";
      }}
    '';
    serviceConfig = {
      RemainAfterExit = true;
      Type = "oneshot";
    };
  };
}
1 Like

Thanks a lot! I will be trying this out. :slight_smile: