diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index 54dd97e5b13..51b472fcf9c 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -137,5 +137,22 @@ import ./make-test-python.nix ({ pkgs, ... }: { # Ensure the two output paths (ls and hello) are in the layer "docker run bulk-layer ls /bin/hello", ) + + with subtest("Ensure correct behavior when no store is needed"): + # This check tests two requirements simultaneously + # 1. buildLayeredImage can build images that don't need a store. + # 2. Layers of symlinks are eliminated by the customization layer. + # + docker.succeed( + "docker load --input='${pkgs.dockerTools.examples.no-store-paths}'" + ) + + # Busybox will not recognize argv[0] and print an error message with argv[0], + # but it confirms that the custom-true symlink is present. + docker.succeed("docker run --rm no-store-paths custom-true |& grep custom-true") + + # This check may be loosened to allow an *empty* store rather than *no* store. + docker.succeed("docker run --rm no-store-paths ls /") + docker.fail("docker run --rm no-store-paths ls /nix/store") ''; }) diff --git a/pkgs/build-support/docker/default.nix b/pkgs/build-support/docker/default.nix index ff9949bc8dd..28c0d2dfcae 100644 --- a/pkgs/build-support/docker/default.nix +++ b/pkgs/build-support/docker/default.nix @@ -319,6 +319,8 @@ rec { enableParallelBuilding = true; } '' + mkdir layers + # Delete impurities for store path layers, so they don't get # shared and taint other projects. cat ${configJson} \ @@ -330,13 +332,12 @@ rec { # created, and that no paths are missed. If you change the # following head and tail call lines, double-check that your # code behaves properly when the number of layers equals: - # maxLayers-1, maxLayers, and maxLayers+1 + # maxLayers-1, maxLayers, and maxLayers+1, 0 paths() { - cat $paths ${lib.concatMapStringsSep " " (path: "| grep -v ${path}") (closures ++ [ overallClosure ])} + cat $paths ${lib.concatMapStringsSep " " (path: "| (grep -v ${path} || true)") (closures ++ [ overallClosure ])} } - # We need to sponge to avoid grep broken pipe error when maxLayers == 1 - paths | sponge | head -n $((maxLayers - 1)) | cat -n | xargs -r -P$NIX_BUILD_CORES -n2 ${storePathToLayer} + paths | head -n $((maxLayers - 1)) | cat -n | xargs -r -P$NIX_BUILD_CORES -n2 ${storePathToLayer} if [ $(paths | wc -l) -ge $maxLayers ]; then paths | tail -n+$maxLayers | xargs ${storePathToLayer} $maxLayers fi diff --git a/pkgs/build-support/docker/examples.nix b/pkgs/build-support/docker/examples.nix index f0dcf236c0e..f42b35e6494 100644 --- a/pkgs/build-support/docker/examples.nix +++ b/pkgs/build-support/docker/examples.nix @@ -258,4 +258,26 @@ rec { maxLayers = 2; }; + # 17. Create a "layered" image without nix store layers. This is not + # recommended, but can be useful for base images in rare cases. + no-store-paths = pkgs.dockerTools.buildLayeredImage { + name = "no-store-paths"; + tag = "latest"; + extraCommands = '' + chmod a+w bin + + # This removes sharing of busybox and is not recommended. We do this + # to make the example suitable as a test case with working binaries. + cp -r ${pkgs.pkgsStatic.busybox}/* . + ''; + contents = [ + # This layer has no dependencies and its symlinks will be dereferenced + # when creating the customization layer. + (pkgs.runCommand "layer-to-flatten" {} '' + mkdir -p $out/bin + ln -s /bin/true $out/bin/custom-true + '' + ) + ]; + }; }