diff --git a/doc/build-helpers/images/dockertools.section.md b/doc/build-helpers/images/dockertools.section.md index 6732f790733d..c1a9526466cd 100644 --- a/doc/build-helpers/images/dockertools.section.md +++ b/doc/build-helpers/images/dockertools.section.md @@ -453,7 +453,7 @@ See [](#ex-dockerTools-streamLayeredImage-exploringlayers) to understand how the `streamLayeredImage` allows scripts to be run when creating the additional layer with symlinks, allowing custom behaviour to affect the final results of the image (see the documentation of the `extraCommands` and `fakeRootCommands` attributes). The resulting repository tarball will list a single image as specified by the `name` and `tag` attributes. -By default, that image will use a static creation date (see documentation for the `created` attribute). +By default, that image will use a static creation date (see documentation for the `created` and `mtime` attributes). This allows the function to produce reproducible images. ### Inputs {#ssec-pkgs-dockerTools-streamLayeredImage-inputs} @@ -516,6 +516,7 @@ This allows the function to produce reproducible images. `created` (String; _optional_) : Specifies the time of creation of the generated image. + This date will be used for the image metadata, and as the default value for `mtime`. This should be either a date and time formatted according to [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) or `"now"`, in which case the current date will be used. :::{.caution} @@ -524,6 +525,18 @@ This allows the function to produce reproducible images. _Default value:_ `"1970-01-01T00:00:01Z"`. +`mtime` (String; _optional_) + +: Specifies the time used for the modification timestamp of files within the layers of the generated image. + This should be either a date and time formatted according to [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) or `"now"`, in which case the current date will be used. + + :::{.caution} + Using a non-constant date will cause built layers to have a different hash each time, preventing deduplication. + Using `"now"` also means that the generated image will not be reproducible anymore (because the date will always change whenever it's built). + ::: + + _Default value:_ the same value as `created`. + `uid` (Number; _optional_) []{#dockerTools-buildLayeredImage-arg-uid} `gid` (Number; _optional_) []{#dockerTools-buildLayeredImage-arg-gid} `uname` (String; _optional_) []{#dockerTools-buildLayeredImage-arg-uname} diff --git a/pkgs/build-support/docker/default.nix b/pkgs/build-support/docker/default.nix index 033bce988ae9..264cfbf82eed 100644 --- a/pkgs/build-support/docker/default.nix +++ b/pkgs/build-support/docker/default.nix @@ -907,6 +907,7 @@ rec { , config ? { } , architecture ? defaultArchitecture , created ? "1970-01-01T00:00:01Z" + , mtime ? created , uid ? 0 , gid ? 0 , uname ? "root" @@ -1009,7 +1010,7 @@ rec { conf = runCommand "${baseName}-conf.json" { - inherit fromImage maxLayers created uid gid uname gname; + inherit fromImage maxLayers created mtime uid gid uname gname; imageName = lib.toLower name; preferLocalBuild = true; passthru.imageTag = @@ -1029,10 +1030,13 @@ rec { imageTag="${tag}" ''} - # convert "created" to iso format + # convert "created" and "mtime" to iso format if [[ "$created" != "now" ]]; then created="$(date -Iseconds -d "$created")" fi + if [[ "$mtime" != "now" ]]; then + mtime="$(date -Iseconds -d "$mtime")" + fi paths() { cat $paths ${lib.concatMapStringsSep " " @@ -1089,6 +1093,7 @@ rec { "customisation_layer", $customisation_layer, "repo_tag": $repo_tag, "created": $created, + "mtime": $mtime, "uid": $uid, "gid": $gid, "uname": $uname, @@ -1100,6 +1105,7 @@ rec { --arg customisation_layer ${customisationLayer} \ --arg repo_tag "$imageName:$imageTag" \ --arg created "$created" \ + --arg mtime "$mtime" \ --arg uid "$uid" \ --arg gid "$gid" \ --arg uname "$uname" \ diff --git a/pkgs/build-support/docker/stream_layered_image.py b/pkgs/build-support/docker/stream_layered_image.py index d3778625ccdf..0078c1cb764e 100644 --- a/pkgs/build-support/docker/stream_layered_image.py +++ b/pkgs/build-support/docker/stream_layered_image.py @@ -307,6 +307,15 @@ def add_bytes(tar, path, content, mtime): tar.addfile(ti, io.BytesIO(content)) +now = datetime.now(tz=timezone.utc) + + +def parse_time(s): + if s == "now": + return now + return datetime.fromisoformat(s) + + def main(): arg_parser = argparse.ArgumentParser( description=""" @@ -342,12 +351,8 @@ Docker Image Specification v1.2 as reference [1]. with open(args.conf, "r") as f: conf = json.load(f) - created = ( - datetime.now(tz=timezone.utc) - if conf["created"] == "now" - else datetime.fromisoformat(conf["created"]) - ) - mtime = int(created.timestamp()) + created = parse_time(conf["created"]) + mtime = int(parse_time(conf["mtime"]).timestamp()) uid = int(conf["uid"]) gid = int(conf["gid"]) uname = conf["uname"]