Building a docker image

Building a docker image is much simpler with Nix compared to writing Dockerfile. Since the entire build process is handled by Nix flakes, most of what’s left to do for docker image creation is copying of the derivations and configuration.

Writing the Nix to build the docker image

Consider a haskell-flake project “foo”. To copy the binaries generated by the default package to /bin on the image, one can use copyToRoot attribute offered by dockerTools.buildImage. For example:

{
  # Inside perSystem
  packages.dockerImage = pkgs.dockerTools.buildImage {
    name = "foo";
    copyToRoot = pkgs.buildEnv {
      paths = with pkgs; [
        self'.packages.default
      ];
      name = "foo-root";
      pathsToLink = [ "/bin" ];
    };
  };
}

In addition to copying over the flake packages, we may also copy paths in the project. self can be added to paths to expose the project directory.

{
  copyToRoot = pkgs.buildEnv {
    paths = with pkgs; [
      coreutils
      bash
      self
    ];
    name = "foo-root";
    pathsToLink = [ "/foo_sub" "/bin" ];
  };
}

If you’d like your docker image to run your haskell project’s default package when the container starts, use the following config:

{
  # Inside dockerImage's `buildImage`
  config = {
    Cmd = [ "${pkgs.lib.getExe self'.packages.default}" ];
  };
}

Build the docker image

To build the docker image as a Nix derivation, run:

nix build .#dockerImage

To load this image into your local docker image registry, run:

docker load -i $(nix build .#dockerImage --print-out-paths)

Tips

Size

Docker images including Haskell packages can be optimized using the methods described here.

Time

If you don’t want docker images showing that the image was created several decades ago, use the following:

{
  # Inside perSystem.packages' `dockerImage`:
  pkgs.dockerTools.buildImage {
    name = "foo";
    created = "now";
  };
}

Tag

If you want to tag the images with the commit id of the working copy:

{
  # Inside perSystem.packages' `dockerImage`:
  pkgs.dockerTools.buildImage {
    name = "foo";
    tag = builtins.substring 0 9 (self.rev or "dev");
  };
}

builtins.substring 0 9 self.rev is the same as git rev-parse --short HEAD. self.rev is non-null only on a clean working copy and hence the tag is set to dev when the working copy is dirty.

SSL certs

In order to be able to make https connections from inside of the docker image, you must expose the cacert Nix package via the relevant environment variable:

{
  # Inside dockerTools.buildImage
  config = {
    Env = [ 
      "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" 
      # Ref: https://hackage.haskell.org/package/x509-system-1.6.7/docs/src/System.X509.Unix.html#getSystemCertificateStore
      "SYSTEM_CERTIFICATE_PATH=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
    ];
  };
}

Example

Links to this page