# Examples of using the docker tools to build packages.
#
# This file defines several docker images. In order to use an image,
# build its derivation with `nix-build`, and then load the result with
# `docker load`. For example:
#
#  $ nix-build '<nixpkgs>' -A dockerTools.examples.redis
#  $ docker load < result

{
  pkgs,
  buildImage,
  buildLayeredImage,
  fakeNss,
  pullImage,
  shadowSetup,
  buildImageWithNixDb,
  pkgsCross,
  streamNixShellImage,
}:

let
  nixosLib = import ../../../nixos/lib {
    # Experimental features need testing too, but there's no point in warning
    # about it, so we enable the feature flag.
    featureFlags.minimalModules = { };
  };
  evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };

  nginxArguments =
    let
      nginxPort = "80";
      nginxConf = pkgs.writeText "nginx.conf" ''
        user nobody nobody;
        daemon off;
        error_log /dev/stdout info;
        pid /dev/null;
        events {}
        http {
          access_log /dev/stdout;
          server {
            listen ${nginxPort};
            index index.html;
            location / {
              root ${nginxWebRoot};
            }
          }
        }
      '';
      nginxWebRoot = pkgs.writeTextDir "index.html" ''
        <html><body><h1>Hello from NGINX</h1></body></html>
      '';
    in
    {
      name = "nginx-container";
      tag = "latest";
      contents = [
        fakeNss
        pkgs.nginx
      ];

      extraCommands = ''
        mkdir -p tmp/nginx_client_body

        # nginx still tries to read this directory even if error_log
        # directive is specifying another file :/
        mkdir -p var/log/nginx
      '';

      config = {
        Cmd = [
          "nginx"
          "-c"
          nginxConf
        ];
        ExposedPorts = {
          "${nginxPort}/tcp" = { };
        };
      };
    };

in

rec {
  # 1. basic example
  bash = buildImage {
    name = "bash";
    tag = "latest";
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      paths = [ pkgs.bashInteractive ];
      pathsToLink = [ "/bin" ];
    };
  };

  # 2. service example, layered on another image
  redis = buildImage {
    name = "redis";
    tag = "latest";

    # for example's sake, we can layer redis on top of bash or debian
    fromImage = bash;
    # fromImage = debian;

    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      paths = [ pkgs.redis ];
      pathsToLink = [ "/bin" ];
    };

    runAsRoot = ''
      mkdir -p /data
    '';

    config = {
      Cmd = [ "/bin/redis-server" ];
      WorkingDir = "/data";
      Volumes = {
        "/data" = { };
      };
    };
  };

  # 3. another service example
  nginx = buildLayeredImage nginxArguments;

  # Used to demonstrate how virtualisation.oci-containers.imageStream works
  nginxStream = pkgs.dockerTools.streamLayeredImage nginxArguments;

  # 4. example of pulling an image. could be used as a base for other images
  nixFromDockerHub = pullImage {
    imageName = "nixos/nix";
    imageDigest = "sha256:85299d86263a3059cf19f419f9d286cc9f06d3c13146a8ebbb21b3437f598357";
    hash = "sha256-xxZ4UW6jRIVAzlVYA62awcopzcYNViDyh6q1yocF3KU=";
    finalImageTag = "2.2.1";
    finalImageName = "nix";
  };
  # Same example, but re-fetches every time the fetcher implementation changes.
  # NOTE: Only use this for testing, or you'd be wasting a lot of time, network and space.
  testNixFromDockerHub = pkgs.testers.invalidateFetcherByDrvHash pullImage {
    imageName = "nixos/nix";
    imageDigest = "sha256:85299d86263a3059cf19f419f9d286cc9f06d3c13146a8ebbb21b3437f598357";
    hash = "sha256-xxZ4UW6jRIVAzlVYA62awcopzcYNViDyh6q1yocF3KU=";
    finalImageTag = "2.2.1";
    finalImageName = "nix";
  };

  # 5. example of multiple contents, emacs and vi happily coexisting
  editors = buildImage {
    name = "editors";
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      pathsToLink = [ "/bin" ];
      paths = [
        pkgs.coreutils
        pkgs.bash
        pkgs.emacs
        pkgs.vim
        pkgs.nano
      ];
    };
  };

  # 6. nix example to play with the container nix store
  # docker run -it --rm nix nix-store -qR $(nix-build '<nixpkgs>' -A nix)
  nix = buildImageWithNixDb {
    name = "nix";
    tag = "latest";
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      pathsToLink = [ "/bin" ];
      paths = [
        # nix-store uses cat program to display results as specified by
        # the image env variable NIX_PAGER.
        pkgs.coreutils
        pkgs.nix
        pkgs.bash
      ];
    };
    config = {
      Env = [
        "NIX_PAGER=cat"
        # A user is required by nix
        # https://github.com/NixOS/nix/blob/9348f9291e5d9e4ba3c4347ea1b235640f54fd79/src/libutil/util.cc#L478
        "USER=nobody"
      ];
    };
  };

  # 7. example of adding something on top of an image pull by our
  # dockerTools chain.
  onTopOfPulledImage = buildImage {
    name = "onTopOfPulledImage";
    tag = "latest";
    fromImage = nixFromDockerHub;
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      pathsToLink = [ "/bin" ];
      paths = [ pkgs.hello ];
    };
  };

  # 8. regression test for erroneous use of eval and string expansion.
  # See issue #34779 and PR #40947 for details.
  runAsRootExtraCommands = pkgs.dockerTools.buildImage {
    name = "runAsRootExtraCommands";
    tag = "latest";
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      pathsToLink = [ "/bin" ];
      paths = [ pkgs.coreutils ];
    };
    # The parens here are to create problematic bash to embed and eval. In case
    # this is *embedded* into the script (with nix expansion) the initial quotes
    # will close the string and the following parens are unexpected
    runAsRoot = ''echo "(runAsRoot)" > runAsRoot'';
    extraCommands = ''echo "(extraCommand)" > extraCommands'';
  };

  # 9. Ensure that setting created to now results in a date which
  # isn't the epoch + 1
  unstableDate = pkgs.dockerTools.buildImage {
    name = "unstable-date";
    tag = "latest";
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      pathsToLink = [ "/bin" ];
      paths = [ pkgs.coreutils ];
    };
    created = "now";
  };

  # 10. Create a layered image
  layered-image = pkgs.dockerTools.buildLayeredImage {
    name = "layered-image";
    tag = "latest";
    extraCommands = ''echo "(extraCommand)" > extraCommands'';
    config.Cmd = [ "${pkgs.hello}/bin/hello" ];
    contents = [
      pkgs.hello
      pkgs.bash
      pkgs.coreutils
    ];
  };

  # 11. Create an image on top of a layered image
  layered-on-top = pkgs.dockerTools.buildImage {
    name = "layered-on-top";
    tag = "latest";
    fromImage = layered-image;
    extraCommands = ''
      mkdir ./example-output
      chmod 777 ./example-output
    '';
    config = {
      Env = [ "PATH=${pkgs.coreutils}/bin/" ];
      WorkingDir = "/example-output";
      Cmd = [
        "${pkgs.bash}/bin/bash"
        "-c"
        "echo hello > foo; cat foo"
      ];
    };
  };

  # 12 Create a layered image on top of a layered image
  layered-on-top-layered = pkgs.dockerTools.buildLayeredImage {
    name = "layered-on-top-layered";
    tag = "latest";
    fromImage = layered-image;
    extraCommands = ''
      mkdir ./example-output
      chmod 777 ./example-output
    '';
    config = {
      Env = [ "PATH=${pkgs.coreutils}/bin/" ];
      WorkingDir = "/example-output";
      Cmd = [
        "${pkgs.bash}/bin/bash"
        "-c"
        "echo hello > foo; cat foo"
      ];
    };
  };

  # 13. example of running something as root on top of a parent image
  # Regression test related to PR #52109
  runAsRootParentImage = buildImage {
    name = "runAsRootParentImage";
    tag = "latest";
    runAsRoot = "touch /example-file";
    fromImage = bash;
  };

  # 14. example of 3 layers images This image is used to verify the
  # order of layers is correct.
  # It allows to validate
  # - the layer of parent are below
  # - the order of parent layer is preserved at image build time
  #   (this is why there are 3 images)
  layersOrder =
    let
      l1 = pkgs.dockerTools.buildImage {
        name = "l1";
        tag = "latest";
        extraCommands = ''
          mkdir -p tmp
          echo layer1 > tmp/layer1
          echo layer1 > tmp/layer2
          echo layer1 > tmp/layer3
        '';
      };
      l2 = pkgs.dockerTools.buildImage {
        name = "l2";
        fromImage = l1;
        tag = "latest";
        extraCommands = ''
          mkdir -p tmp
          echo layer2 > tmp/layer2
          echo layer2 > tmp/layer3
        '';
      };
    in
    pkgs.dockerTools.buildImage {
      name = "l3";
      fromImage = l2;
      tag = "latest";
      copyToRoot = pkgs.buildEnv {
        name = "image-root";
        pathsToLink = [ "/bin" ];
        paths = [ pkgs.coreutils ];
      };
      extraCommands = ''
        mkdir -p tmp
        echo layer3 > tmp/layer3
      '';
    };

  # 15. Environment variable inheritance.
  # Child image should inherit parents environment variables,
  # optionally overriding them.
  environmentVariablesParent = pkgs.dockerTools.buildImage {
    name = "parent";
    tag = "latest";
    config = {
      Env = [
        "FROM_PARENT=true"
        "LAST_LAYER=parent"
      ];
    };
  };

  environmentVariables = pkgs.dockerTools.buildImage {
    name = "child";
    fromImage = environmentVariablesParent;
    tag = "latest";
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      pathsToLink = [ "/bin" ];
      paths = [ pkgs.coreutils ];
    };
    config = {
      Env = [
        "FROM_CHILD=true"
        "LAST_LAYER=child"
      ];
    };
  };

  environmentVariablesLayered = pkgs.dockerTools.buildLayeredImage {
    name = "child";
    fromImage = environmentVariablesParent;
    tag = "latest";
    contents = [ pkgs.coreutils ];
    config = {
      Env = [
        "FROM_CHILD=true"
        "LAST_LAYER=child"
      ];
    };
  };

  # 16. Create another layered image, for comparing layers with image 10.
  another-layered-image = pkgs.dockerTools.buildLayeredImage {
    name = "another-layered-image";
    tag = "latest";
    config.Cmd = [ "${pkgs.hello}/bin/hello" ];
  };

  # 17. Create a layered image with only 2 layers
  two-layered-image = pkgs.dockerTools.buildLayeredImage {
    name = "two-layered-image";
    tag = "latest";
    config.Cmd = [ "${pkgs.hello}/bin/hello" ];
    contents = [
      pkgs.bash
      pkgs.hello
    ];
    maxLayers = 2;
  };

  # 18. Create a layered image with more packages than max layers.
  # coreutils and hello are part of the same layer
  bulk-layer = pkgs.dockerTools.buildLayeredImage {
    name = "bulk-layer";
    tag = "latest";
    contents = with pkgs; [
      coreutils
      hello
    ];
    maxLayers = 2;
  };

  # 19. Create a layered image with a base image and more packages than max
  # layers. coreutils and hello are part of the same layer
  layered-bulk-layer = pkgs.dockerTools.buildLayeredImage {
    name = "layered-bulk-layer";
    tag = "latest";
    fromImage = two-layered-image;
    contents = with pkgs; [
      coreutils
      hello
    ];
    maxLayers = 4;
  };

  # 20. 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 = ''
      # 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}/* .

      # This is a "build" dependency that will not appear in the image
      ${pkgs.hello}/bin/hello
    '';
  };

  nixLayered = pkgs.dockerTools.buildLayeredImageWithNixDb {
    name = "nix-layered";
    tag = "latest";
    contents = [
      # nix-store uses cat program to display results as specified by
      # the image env variable NIX_PAGER.
      pkgs.coreutils
      pkgs.nix
      pkgs.bash
    ];
    config = {
      Env = [
        "NIX_PAGER=cat"
        # A user is required by nix
        # https://github.com/NixOS/nix/blob/9348f9291e5d9e4ba3c4347ea1b235640f54fd79/src/libutil/util.cc#L478
        "USER=nobody"
      ];
    };
  };

  # 21. Support files in the store on buildLayeredImage
  # See: https://github.com/NixOS/nixpkgs/pull/91084#issuecomment-653496223
  filesInStore = pkgs.dockerTools.buildLayeredImageWithNixDb {
    name = "file-in-store";
    tag = "latest";
    contents = [
      pkgs.coreutils
      pkgs.nix
      (pkgs.writeScriptBin "myscript" ''
        #!${pkgs.runtimeShell}
        cat ${pkgs.writeText "somefile" "some data"}
      '')
    ];
    config = {
      Cmd = [ "myscript" ];
      # For some reason 'nix-store --verify' requires this environment variable
      Env = [ "USER=root" ];
    };
  };

  # 22. Ensure that setting created to now results in a date which
  # isn't the epoch + 1 for layered images.
  unstableDateLayered = pkgs.dockerTools.buildLayeredImage {
    name = "unstable-date-layered";
    tag = "latest";
    contents = [ pkgs.coreutils ];
    created = "now";
  };

  # 23. Ensure that layers are unpacked in the correct order before the
  # runAsRoot script is executed.
  layersUnpackOrder =
    let
      layerOnTopOf =
        parent: layerName:
        pkgs.dockerTools.buildImage {
          name = "layers-unpack-order-${layerName}";
          tag = "latest";
          fromImage = parent;
          copyToRoot = pkgs.buildEnv {
            name = "image-root";
            pathsToLink = [ "/bin" ];
            paths = [ pkgs.coreutils ];
          };
          runAsRoot = ''
            #!${pkgs.runtimeShell}
            echo -n "${layerName}" >> /layer-order
          '';
        };
      # When executing the runAsRoot script when building layer C, if layer B is
      # not unpacked on top of layer A, the contents of /layer-order will not be
      # "ABC".
      layerA = layerOnTopOf null "a";
      layerB = layerOnTopOf layerA "b";
      layerC = layerOnTopOf layerB "c";
    in
    layerC;

  bashUncompressed = pkgs.dockerTools.buildImage {
    name = "bash-uncompressed";
    tag = "latest";
    compressor = "none";
    # Not recommended. Use `buildEnv` between copy and packages to avoid file duplication.
    copyToRoot = pkgs.bashInteractive;
  };

  bashZstdCompressed = pkgs.dockerTools.buildImage {
    name = "bash-zstd";
    tag = "latest";
    compressor = "zstd";
    # Not recommended. Use `buildEnv` between copy and packages to avoid file duplication.
    copyToRoot = pkgs.bashInteractive;
  };

  # buildImage without explicit tag
  bashNoTag = pkgs.dockerTools.buildImage {
    name = "bash-no-tag";
    # Not recommended. Use `buildEnv` between copy and packages to avoid file duplication.
    copyToRoot = pkgs.bashInteractive;
  };

  # buildLayeredImage without explicit tag
  bashNoTagLayered = pkgs.dockerTools.buildLayeredImage {
    name = "bash-no-tag-layered";
    contents = pkgs.bashInteractive;
  };

  # buildLayeredImage without compression
  bashLayeredUncompressed = pkgs.dockerTools.buildLayeredImage {
    name = "bash-layered-uncompressed";
    tag = "latest";
    compressor = "none";
    contents = pkgs.bashInteractive;
  };

  # buildLayeredImage with zstd compression
  bashLayeredZstdCompressed = pkgs.dockerTools.buildLayeredImage {
    name = "bash-layered-zstd";
    tag = "latest";
    compressor = "zstd";
    contents = pkgs.bashInteractive;
  };

  # streamLayeredImage without explicit tag
  bashNoTagStreamLayered = pkgs.dockerTools.streamLayeredImage {
    name = "bash-no-tag-stream-layered";
    contents = pkgs.bashInteractive;
  };

  # buildLayeredImage with non-root user
  bashLayeredWithUser =
    let
      nonRootShadowSetup =
        {
          user,
          uid,
          gid ? uid,
        }:
        with pkgs;
        [
          (writeTextDir "etc/shadow" ''
            root:!x:::::::
            ${user}:!:::::::
          '')
          (writeTextDir "etc/passwd" ''
            root:x:0:0::/root:${runtimeShell}
            ${user}:x:${toString uid}:${toString gid}::/home/${user}:
          '')
          (writeTextDir "etc/group" ''
            root:x:0:
            ${user}:x:${toString gid}:
          '')
          (writeTextDir "etc/gshadow" ''
            root:x::
            ${user}:x::
          '')
        ];
    in
    pkgs.dockerTools.buildLayeredImage {
      name = "bash-layered-with-user";
      tag = "latest";
      contents =
        [
          pkgs.bash
          pkgs.coreutils
        ]
        ++ nonRootShadowSetup {
          uid = 999;
          user = "somebody";
        };
    };

  # basic example, with cross compilation
  cross =
    let
      # Cross compile for x86_64 if on aarch64
      crossPkgs =
        if pkgs.stdenv.hostPlatform.system == "aarch64-linux" then
          pkgsCross.gnu64
        else
          pkgsCross.aarch64-multiplatform;
    in
    crossPkgs.dockerTools.buildImage {
      name = "hello-cross";
      tag = "latest";
      copyToRoot = pkgs.buildEnv {
        name = "image-root";
        pathsToLink = [ "/bin" ];
        paths = [ crossPkgs.hello ];
      };
    };

  # layered image where a store path is itself a symlink
  layeredStoreSymlink =
    let
      target = pkgs.writeTextDir "dir/target" "Content doesn't matter.";
      symlink = pkgs.runCommand "symlink" { } "ln -s ${target} $out";
    in
    pkgs.dockerTools.buildLayeredImage {
      name = "layeredstoresymlink";
      tag = "latest";
      contents = [
        pkgs.bash
        symlink
      ];
    }
    // {
      passthru = { inherit symlink; };
    };

  # image with registry/ prefix
  prefixedImage = pkgs.dockerTools.buildImage {
    name = "registry-1.docker.io/image";
    tag = "latest";
    config.Cmd = [ "${pkgs.hello}/bin/hello" ];
  };

  # layered image with registry/ prefix
  prefixedLayeredImage = pkgs.dockerTools.buildLayeredImage {
    name = "registry-1.docker.io/layered-image";
    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/alice
      chown 1000 ./home/alice
      ln -s ${
        pkgs.hello.overrideAttrs (
          finalAttrs: prevAttrs: {
            # A unique `hello` to make sure that it isn't included via another mechanism by accident.
            configureFlags = prevAttrs.configureFlags or [ ] ++ [
              " --program-prefix=layeredImageWithFakeRootCommands-"
            ];
            doCheck = false;
            versionCheckProgram = "${builtins.placeholder "out"}/bin/${finalAttrs.meta.mainProgram}";
            meta = prevAttrs.meta // {
              mainProgram = "layeredImageWithFakeRootCommands-hello";
            };
          }
        )
      } ./hello
    '';
  };

  # tarball consisting of both bash and redis images
  mergedBashAndRedis = pkgs.dockerTools.mergeImages [
    bash
    redis
  ];

  # tarball consisting of bash (without tag) and redis images
  mergedBashNoTagAndRedis = pkgs.dockerTools.mergeImages [
    bashNoTag
    redis
  ];

  # tarball consisting of bash and layered image with different owner of the
  # /home/alice directory
  mergedBashFakeRoot = pkgs.dockerTools.mergeImages [
    bash
    layeredImageWithFakeRootCommands
  ];

  mergeVaryingCompressor = pkgs.dockerTools.mergeImages [
    redis
    bashUncompressed
    bashZstdCompressed
  ];

  helloOnRoot = pkgs.dockerTools.streamLayeredImage {
    name = "hello";
    tag = "latest";
    contents = [
      (pkgs.buildEnv {
        name = "hello-root";
        paths = [ pkgs.hello ];
      })
    ];
    config.Cmd = [ "hello" ];
  };

  helloOnRootNoStore = pkgs.dockerTools.streamLayeredImage {
    name = "hello";
    tag = "latest";
    contents = [
      (pkgs.buildEnv {
        name = "hello-root";
        paths = [ pkgs.hello ];
      })
    ];
    config.Cmd = [ "hello" ];
    includeStorePaths = false;
  };

  helloOnRootNoStoreFakechroot = pkgs.dockerTools.streamLayeredImage {
    name = "hello";
    tag = "latest";
    contents = [
      (pkgs.buildEnv {
        name = "hello-root";
        paths = [ pkgs.hello ];
      })
    ];
    config.Cmd = [ "hello" ];
    includeStorePaths = false;
    enableFakechroot = true;
  };

  etc =
    let
      inherit (pkgs) lib;
      nixosCore = (
        evalMinimalConfig (
          { config, ... }:
          {
            imports = [
              pkgs.pkgsModule
              ../../../nixos/modules/system/etc/etc.nix
            ];
            environment.etc."some-config-file" = {
              text = ''
                127.0.0.1 localhost
                ::1 localhost
              '';
              # For executables:
              # mode = "0755";
            };
          }
        )
      );
    in
    pkgs.dockerTools.streamLayeredImage {
      name = "etc";
      tag = "latest";
      enableFakechroot = true;
      fakeRootCommands = ''
        mkdir -p /etc
        ${nixosCore.config.system.build.etcActivationCommands}
      '';
      config.Cmd = pkgs.writeScript "etc-cmd" ''
        #!${pkgs.busybox}/bin/sh
        ${pkgs.busybox}/bin/cat /etc/some-config-file
      '';
    };

  # Example export of the bash image
  exportBash = pkgs.dockerTools.exportImage { fromImage = bash; };

  imageViaFakeChroot = pkgs.dockerTools.streamLayeredImage {
    name = "image-via-fake-chroot";
    tag = "latest";
    config.Cmd = [ "hello" ];
    enableFakechroot = true;
    # Crucially, instead of a relative path, this creates /bin, which is
    # intercepted by fakechroot.
    # This functionality is not available on darwin as of 2021.
    fakeRootCommands = ''
      mkdir /bin
      ln -s ${pkgs.hello}/bin/hello /bin/hello
    '';
  };

  build-image-with-path = buildImage {
    name = "build-image-with-path";
    tag = "latest";
    # Not recommended. Use `buildEnv` between copy and packages to avoid file duplication.
    copyToRoot = [
      pkgs.bashInteractive
      ./test-dummy
    ];
  };

  layered-image-with-path = pkgs.dockerTools.streamLayeredImage {
    name = "layered-image-with-path";
    tag = "latest";
    contents = [
      pkgs.bashInteractive
      ./test-dummy
    ];
  };

  build-image-with-architecture = buildImage {
    name = "build-image-with-architecture";
    tag = "latest";
    architecture = "arm64";
    # Not recommended. Use `buildEnv` between copy and packages to avoid file duplication.
    copyToRoot = [
      pkgs.bashInteractive
      ./test-dummy
    ];
  };

  layered-image-with-architecture = pkgs.dockerTools.streamLayeredImage {
    name = "layered-image-with-architecture";
    tag = "latest";
    architecture = "arm64";
    contents = [
      pkgs.bashInteractive
      ./test-dummy
    ];
  };

  # ensure that caCertificates builds
  image-with-certs = buildImage {
    name = "image-with-certs";
    tag = "latest";

    copyToRoot = pkgs.buildEnv {
      name = "image-with-certs-root";
      paths = [
        pkgs.coreutils
        pkgs.dockerTools.caCertificates
      ];
    };

    config = {
    };
  };

  nix-shell-basic = streamNixShellImage {
    name = "nix-shell-basic";
    tag = "latest";
    drv = pkgs.hello;
  };

  nix-shell-hook = streamNixShellImage {
    name = "nix-shell-hook";
    tag = "latest";
    drv = pkgs.mkShell {
      shellHook = ''
        echo "This is the shell hook!"
        exit
      '';
    };
  };

  nix-shell-inputs = streamNixShellImage {
    name = "nix-shell-inputs";
    tag = "latest";
    drv = pkgs.mkShell {
      nativeBuildInputs = [
        pkgs.hello
      ];
    };
    command = ''
      hello
    '';
  };

  nix-shell-pass-as-file = streamNixShellImage {
    name = "nix-shell-pass-as-file";
    tag = "latest";
    drv = pkgs.mkShell {
      str = "this is a string";
      passAsFile = [ "str" ];
    };
    command = ''
      cat "$strPath"
    '';
  };

  nix-shell-run = streamNixShellImage {
    name = "nix-shell-run";
    tag = "latest";
    drv = pkgs.mkShell { };
    run = ''
      case "$-" in
      *i*) echo This shell is interactive ;;
      *) echo This shell is not interactive ;;
      esac
    '';
  };

  nix-shell-command = streamNixShellImage {
    name = "nix-shell-command";
    tag = "latest";
    drv = pkgs.mkShell { };
    command = ''
      case "$-" in
      *i*) echo This shell is interactive ;;
      *) echo This shell is not interactive ;;
      esac
    '';
  };

  nix-shell-writable-home = streamNixShellImage {
    name = "nix-shell-writable-home";
    tag = "latest";
    drv = pkgs.mkShell { };
    run = ''
      if [[ "$HOME" != "$(eval "echo ~$(whoami)")" ]]; then
        echo "\$HOME ($HOME) is not the same as ~\$(whoami) ($(eval "echo ~$(whoami)"))"
        exit 1
      fi

      if ! touch $HOME/test-file; then
        echo "home directory is not writable"
        exit 1
      fi
      echo "home directory is writable"
    '';
  };

  nix-shell-nonexistent-home = streamNixShellImage {
    name = "nix-shell-nonexistent-home";
    tag = "latest";
    drv = pkgs.mkShell { };
    homeDirectory = "/homeless-shelter";
    run = ''
      if [[ "$HOME" != "$(eval "echo ~$(whoami)")" ]]; then
        echo "\$HOME ($HOME) is not the same as ~\$(whoami) ($(eval "echo ~$(whoami)"))"
        exit 1
      fi

      if -e $HOME; then
        echo "home directory exists"
        exit 1
      fi
      echo "home directory doesn't exist"
    '';
  };

  nix-shell-build-derivation = streamNixShellImage {
    name = "nix-shell-build-derivation";
    tag = "latest";
    drv = pkgs.hello;
    run = ''
      buildDerivation
      $out/bin/hello
    '';
  };

  nix-layered = pkgs.dockerTools.streamLayeredImage {
    name = "nix-layered";
    tag = "latest";
    contents = [
      pkgs.nix
      pkgs.bash
    ];
    includeNixDB = true;
    config = {
      Env = [
        "NIX_PAGER=cat"
      ];
    };
  };

}