diff --git a/doc/builders/images/dockertools.section.md b/doc/builders/images/dockertools.section.md index 23662f9bbfe4..2d21eb1c2e07 100644 --- a/doc/builders/images/dockertools.section.md +++ b/doc/builders/images/dockertools.section.md @@ -147,6 +147,10 @@ Create a Docker image with many of the store paths being on their own layer to i : Shell commands to run while building the final layer, without access to most of the layer contents. Changes to this layer are "on top" of all the other layers, so can create additional directories and files. +`fakeRootCommands` _optional_ + +: Shell commands to run while creating the archive for the final layer in a fakeroot environment. Unlike `extraCommands`, you can run `chown` to change the owners of the files in the archive, changing fakeroot's state instead of the real filesystem. The latter would require privileges that the build user does not have. Static binaries do not interact with the fakeroot environment. By default all files in the archive will be owned by root. + ### Behavior of `contents` in the final image {#dockerTools-buildLayeredImage-arg-contents} Each path directly listed in `contents` will have a symlink in the root of the image. diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index a0e81b613ce8..80d527b453fa 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -313,5 +313,13 @@ import ./make-test-python.nix ({ pkgs, ... }: { docker.succeed( "docker images --format '{{.Repository}}' | grep -F '${examples.prefixedLayeredImage.imageName}'" ) + + with subtest("buildLayeredImage supports running chown with fakeRootCommands"): + docker.succeed( + "docker load --input='${examples.layeredImageWithFakeRootCommands}'" + ) + docker.succeed( + "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/jane | grep -E ^1000$'" + ) ''; }) diff --git a/pkgs/build-support/docker/default.nix b/pkgs/build-support/docker/default.nix index a73737cb1231..b03bfcca87f4 100644 --- a/pkgs/build-support/docker/default.nix +++ b/pkgs/build-support/docker/default.nix @@ -7,6 +7,7 @@ coreutils, docker, e2fsprogs, + fakeroot, findutils, go, jq, @@ -740,6 +741,9 @@ rec { created ? "1970-01-01T00:00:01Z", # Optional bash script to run on the files prior to fixturizing the layer. extraCommands ? "", + # Optional bash script to run inside fakeroot environment. + # Could be used for changing ownership of files in customisation layer. + fakeRootCommands ? "", # We pick 100 to ensure there is plenty of room for extension. I # believe the actual maximum is 128. maxLayers ? 100 @@ -765,19 +769,24 @@ rec { customisationLayer = symlinkJoin { name = "${baseName}-customisation-layer"; paths = contentsList; - inherit extraCommands; + inherit extraCommands fakeRootCommands; + nativeBuildInputs = [ fakeroot ]; postBuild = '' mv $out old_out (cd old_out; eval "$extraCommands" ) mkdir $out - tar \ - --sort name \ - --owner 0 --group 0 --mtime "@$SOURCE_DATE_EPOCH" \ - --hard-dereference \ - -C old_out \ - -cf $out/layer.tar . + fakeroot bash -c ' + source $stdenv/setup + cd old_out + eval "$fakeRootCommands" + tar \ + --sort name \ + --numeric-owner --mtime "@$SOURCE_DATE_EPOCH" \ + --hard-dereference \ + -cf $out/layer.tar . + ' sha256sum $out/layer.tar \ | cut -f 1 -d ' ' \ diff --git a/pkgs/build-support/docker/examples.nix b/pkgs/build-support/docker/examples.nix index 9c7d46812140..f6b468942fe0 100644 --- a/pkgs/build-support/docker/examples.nix +++ b/pkgs/build-support/docker/examples.nix @@ -484,4 +484,17 @@ rec { tag = "latest"; config.Cmd = [ "${pkgs.hello}/bin/hello" ]; }; + + # layered image with files owned by a user other than root + layeredImageWithFakeRootCommands = pkgs.dockerTools.buildLayeredImage { + name = "layered-image-with-fake-root-commands"; + tag = "latest"; + contents = [ + pkgs.pkgsStatic.busybox + ]; + fakeRootCommands = '' + mkdir -p ./home/jane + chown 1000 ./home/jane + ''; + }; }