# \`make-shell\`, a modular replacement for \`mkShell\`

**URL:** https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509
**Category:** Announcements
**Created:** [July 5, 2024, 4:33pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509 "2024-07-05T16:33:55Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [July 5, 2024, 4:33pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/1 "2024-07-05T16:33:56Z")

</div>

Turn THIS:

```nix
pkgs.mkShell {
  packages = [pkgs.curl pkgs.git pkgs.jq pkgs.wget];
};

```

into THIS!

```auto
pkgs.make-shell {
  packages = [pkgs.curl pkgs.git pkgs.jq pkgs.wget];
};

```

😅 Alright, I admit that [this project](https://github.com/nicknovitski/make-shell) won’t help many simple use cases, but changing `mkShell` to accept a module as its argument is helping me manage a herd of devShells, using `imports` to manage common features, and to define new options.

I wrote a full rationale for the project [in a WHY.md document](https://github.com/nicknovitski/make-shell/blob/40188294135c3ad320daa7d2597cb1af43eb04d6/WHY.md), which might tell you if you could get any use from it, but if you prefer to see that by example, here’s a “Shell Module” which adds and authenticates a configurable Google Cloud CLI package:

```nix
{
  lib,
  pkgs,
  config,
  ...
}: {
  options.gcloud.enable = lib.mkEnableOption "install google cloud sdk";
  options.gcloud.extraComponents =
    lib.mkOption
    {
      description = "IDs of Google Cloud SDK components to install (list them with `gcloud components list`)";
      default = [];
      example = ["gke-gcloud-auth-plugin"];
      type = lib.types.listOf lib.types.nonEmptyStr;
    };
  config = lib.mkIf config.gcloud.enable {
    packages = [
      (
        if config.gcloud.extraComponents == []
        then pkgs.google-cloud-sdk
        else
          pkgs.google-cloud-sdk.withExtraComponents
          (builtins.map (c: pkgs.google-cloud-sdk.components.${c})
            config.gcloud.extraComponents)
      )
    ]; 
    shellHook = ''
        gcloudAccount=$(gcloud auth list --filter=status:ACTIVE --format="value(account)")
        if [-z ''${gcloudAccount:+set}]; then
          echo "Google Cloud SDK is not authorized"
          read -rp "In the browser tab about to open, authenticate to your Google Cloud account. (press enter to continue)"
          gcloud auth login
        fi
        if ! gcloud auth application-default print-access-token&>/dev/null; then
          read -rp "You don't have any default application credentials for Google Cloud. In the browser tab about to open, authorize the use of your Google Cloud account. (press enter to continue)"
          gcloud auth application-default login
        fi
    '';
  };
}

```

…and here’s a simple option which uses that “gcloud” module to set a developer up to authenticate with clusters running in google cloud’s kubernetes engine:

```nix
{
  lib,
  pkgs,
  config,
  ...
}: {
    options.k8s.enable = lib.mkEnableOption "kubernetes tools";
    config = lib.mkIf config.k8s.enable {
        packages = [pkgs.kubectl pkgs.minikube];
        gcloud.enable = true;
        gcloud.extraComponents = ["gke-gcloud-auth-plugin"];
    };
};

```

Commit those files to a flake and then you could write a consuming flake like this:

```auto
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    make-shell.url = "github:nicknovitski/make-shell";
    my-org-nix.url = "github:myOrg/nix-config"
  };

  outputs = {
    self,
    nixpkgs,
    flake-utils,
    make-shell,
    my-org-nix,
  }:
    flake-utils.lib.eachDefaultSystem (system: let
      pkgs = import nixpkgs {
        inherit system;
        config = {};
        overlays = [make-shell.overlays.default];
      };
    in {
      devShells.gcloud-function = pkgs.make-shell {
        imports = [my-org-nix.shellModules.default];
        gcloud.enable = true;
      };
     devShells.kubernetes = pkgs.make-shell {
       imports = [my-org-nix.shellModules.default];
       k8s.enable = true;
     };
    });
}

```

It also has a flake.parts flake module, if you like those. I hope at least a few people find it useful! 😄

---

<div class="post-metadata">

### Author: ![jjpe](https://discourse.nixos.org/user_avatar/discourse.nixos.org/jjpe/32/7779_2.png) [@jjpe](https://discourse.nixos.org/u/jjpe)
#### Post date: [July 5, 2024, 4:41pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/2 "2024-07-05T16:41:16Z")

</div>

> [@nicknovitski](#):
>
> Turn THIS:
> 
> ```auto
> pkgs.mkshell {
> packages = [pkgs.curl pkgs.git pkgs.jq pkgs.wget];
> };
> 
> ```
> 
> into THIS!
> 
> ```auto
> pkgs.make-shell {
> packages = [pkgs.curl pkgs.git pkgs.jq pkgs.wget];
> };
> 
> ```

The argument to each of these fns is identical. As-is, I don’t see the point of `pkgs.make-shell` _at all_.

Could you come up with an example that actually demonstrates the virtues `pkgs.make-shell` is supposed to have?

---

<div class="post-metadata">

### Author: ![piegames](https://discourse.nixos.org/user_avatar/discourse.nixos.org/piegames/32/5324_2.png) [@piegames](https://discourse.nixos.org/u/piegames)
#### Post date: [July 5, 2024, 6:45pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/3 "2024-07-05T18:45:07Z")

</div>

Please read the rest of the post

---

<div class="post-metadata">

### Author: ![jjpe](https://discourse.nixos.org/user_avatar/discourse.nixos.org/jjpe/32/7779_2.png) [@jjpe](https://discourse.nixos.org/u/jjpe)
#### Post date: [July 5, 2024, 6:53pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/4 "2024-07-05T18:53:46Z")

</div>

I did, and yet I wrote that post. What’s that tell ya?

---

<div class="post-metadata">

### Author: ![jchw](https://discourse.nixos.org/user_avatar/discourse.nixos.org/jchw/32/1499_2.png) [@jchw](https://discourse.nixos.org/u/jchw)
#### Post date: [July 5, 2024, 6:56pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/5 "2024-07-05T18:56:08Z")

</div>

Well, the rest of the post is an example of using it in a way that shows off what it’s useful for. It’s mkShell but modular, because the input is a NixOS module, that can include other modules and configuration/options.

---

<div class="post-metadata">

### Author: ![lbodor](https://discourse.nixos.org/user_avatar/discourse.nixos.org/lbodor/32/2892_2.png) [@lbodor](https://discourse.nixos.org/u/lbodor)
#### Post date: [July 5, 2024, 9:19pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/6 "2024-07-05T21:19:17Z")

</div>

👍 Thank you very much for sharing make-shell. It is exactly the lean alternative to existing options I’d been hoping someone would write.

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [July 6, 2024, 5:24pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/7 "2024-07-06T17:24:00Z")

</div>

Haha! I’m not bothered if `make-shell` seems useless to you; as my joking first example was meant to show, in many situations, it _is_ useless! 😄

Also I do struggle to explain where it _isn’t_ useless in a small number of words. In [the project’s WHY](https://github.com/nicknovitski/make-shell/blob/40188294135c3ad320daa7d2597cb1af43eb04d6/WHY.md), I try to describe the situations where I think people could get the most benefit from it, and highlight all of the contrasting situations where I think they wouldn’t. I wrote that document because I know there are many, many users of nix, and even of `mkShell` who have no strong reason to use `make-shell`, and I want them to be able to recognize that as soon as possible.

I linked the WHY document instead of copying it to my original post because it’s long, but I can summarize the primary experience that made me want to write `make-shell`: it was working across 10+ flakes in one organization, all with at least one devShell, in _all_ of which developers expected a few common dependencies to be present, but which some developers didn’t want to install globally.

Here’s how to achieve that with `make-shell`:

```nix
# my-org.nix/flake.nix
https://flake.parts/options/make-shell
outputs: _ {
  shellModules.default = { pkgs, ...}: { packages = [pkgs.jq pkgs.yq pkgs.fd]; };
}
# 
# our-python-project/flake.nix
inputs.make-shell.url = "github:nicknovitski/make-shell";
inputs.my-org-nix = "path:../my-org.nix";
outputs = {
    nixpkgs,
    flake-utils,
    make-shell,
    my-org-nix,
    ...
  }:
    flake-utils.lib.eachDefaultSystem (system: let
      pkgs = import nixpkgs {
        inherit system;
        config = {};
        overlays = [make-shell.overlays.default];
      };
    in {
  devShells.default = pkgs.make-shell {
    imports = [my-org-nix.shellModules.default];
    packages = [pkgs.python3]; # option definitions here are merged with the ones in the imported modules
  };
}

```

Is that a clearer use-case?

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [July 6, 2024, 5:39pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/8 "2024-07-06T17:39:45Z")

</div>

Thank you so much for saying this, that’s exactly the use I hope people have from it. It’s true that `make-shell` is just a much, much worse version of devenv and devshell. It’s much less user-friendly, it has no CLI, no config file standard, almost no “features” at all! But if you’re repeating yourself in writing shell definitions for your flakes, you could use it to write as many of those missing features which you want and have time for. And maybe we can move towards open source collections of re-usable Shell Modules which could be re-used by those superior projects? I intend to write one of those next, to see if it’s a good idea or not.

Oh, and the [flake module’s documentation has been published on flake.parts now](https://flake.parts/options/make-shell).

---

<div class="post-metadata">

### Author: ![emily](https://discourse.nixos.org/user_avatar/discourse.nixos.org/emily/32/1628_2.png) [@emily](https://discourse.nixos.org/u/emily)
#### Post date: [July 6, 2024, 5:44pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/9 "2024-07-06T17:44:31Z")

</div>

I like things that are much worse. Have you considered offering a variant that makes a “bare” shell without any of the usual `stdenv` stuff? Getting a direnv setup that doesn’t set 500 environment variables or override the macOS shell utilities with GNU coreutils is most of why I use devshell.

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [July 6, 2024, 5:56pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/10 "2024-07-06T17:56:32Z")

</div>

I have written modules which make a little progress towards that, although I haven’t decided whether or where to publish them.

One of `make-shell`’s options is `function`, which is `pkgs.mkShell` by default, but can be replaced with any function which supports the same arguments. And one actual “feature” that `make-shell` has over `mkShell` is that the `env` option can contain attributes whose value is `null`, which causes the variable of that attribute’s name to be `unset` in the shell.

So a simple module which used both of those features to slim things down a bit could look like this:

```nix
{pkgs,...}: {
  function = pkgs.mkShellNoCC;
  env = { # credit to https://github.com/cachix/devenv/blob/7b3ed618571f0d14655b05f7b1c6ef600904383a/src/modules/top-level.nix#L79
    HOST_PATH = null;
    NIX_BUILD_CORES = null;
    __structuredAttrs = null;
    buildInputs = null;
    buildPhase = null;
    builder = null;
    depsBuildBuild = null;
    depsBuildBuildPropagated = null;
    depsBuildTarget = null;
    depsBuildTargetPropagated = null;
    depsHostHost = null;
    depsHostHostPropagated = null;
    depsTargetTarget = null;
    depsTargetTargetPropagated = null;
    dontAddDisableDepTrack = null;
    doCheck = null;
    doInstallCheck = null;
    nativeBuildInputs = null;
    out = null;
    outputs = null;
    patches = null;
    phases = null;
    preferLocalBuild = null;
    propagatedBuildInputs = null;
    propagatedNativeBuildInputs = null;
    shell = null;
    shellHook = null;
    stdenv = null;
    strictDeps = null;
  };
}

```

But I’m sure this could be improved a great deal, especially for `function`.

---

<div class="post-metadata">

### Author: ![emily](https://discourse.nixos.org/user_avatar/discourse.nixos.org/emily/32/1628_2.png) [@emily](https://discourse.nixos.org/u/emily)
#### Post date: [July 6, 2024, 6:05pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/11 "2024-07-06T18:05:45Z")

</div>

Yeah, I think starting from scratch rather than trying to trim `mkShell` down to size makes more sense. devshell has [`mkNakedShell`](https://github.com/numtide/devshell/blob/1ebbe68d57457c8cae98145410b164b5477761f4/nix/mkNakedShell.nix), but it’s not publicly‐exposed (at least in the flake).

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [July 7, 2024, 8:04pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/12 "2024-07-07T20:04:08Z")

</div>

I’ve been subscribed to this PR for a while, it may show a good way forward, whether or not it’s right for nixpkgs: [mkShellMinimal: Create an ultra minimal nix-shell](https://github.com/NixOS/nixpkgs/pull/132617)

---

<div class="post-metadata">

### Author: ![fzakaria](https://discourse.nixos.org/user_avatar/discourse.nixos.org/fzakaria/32/2643_2.png) [@fzakaria](https://discourse.nixos.org/u/fzakaria)
#### Post date: [July 7, 2024, 8:20pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/13 "2024-07-07T20:20:30Z")

</div>

I always thought my PR for a minimal shell then adding layers (I guess it could be modules) could be a viable alternative.

You could build up the default mkShell back from it.

I wish I landed that PR you linked 😭

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [July 9, 2024, 4:16pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/14 "2024-07-09T16:16:09Z")

</div>

I thought it might also be helpful to have a fully worked shell module as an example, so I made this: [GitHub - nicknovitski/javascript-nix: `make-shell` nix modules for javascript development](https://github.com/nicknovitski/javascript-nix).

Actually, I know it will be helpful…to me! But I hope it will be helpful to other people too, whether to see what’s possible with make-shell, or as the start of configuration for a project or two.

e: I already hope to extend it to have a version-selection capability in the vein of the very impressive nixpkgs-python.

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [July 9, 2024, 4:19pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/15 "2024-07-09T16:19:20Z")

</div>

Oh, interesting. So it wouldn’t be impossible to maintain the mkShell interface while slimming down the implementation to something like your minimal shell…that seems promising!

---

<div class="post-metadata">

### Author: ![jjpe](https://discourse.nixos.org/user_avatar/discourse.nixos.org/jjpe/32/7779_2.png) [@jjpe](https://discourse.nixos.org/u/jjpe)
#### Post date: [July 9, 2024, 5:58pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/16 "2024-07-09T17:58:56Z")

</div>

It definitely is, thank you for that explanation 🙂

---

<div class="post-metadata">

### Author: ![lucasew](https://discourse.nixos.org/user_avatar/discourse.nixos.org/lucasew/32/3317_2.png) [@lucasew](https://discourse.nixos.org/u/lucasew)
#### Post date: [July 10, 2024, 9:56pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/17 "2024-07-10T21:56:30Z")

</div>

What is the differential for this compared to cachix devenv?

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [July 12, 2024, 6:26pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/18 "2024-07-12T18:26:47Z")

</div>

As I said above, but I don’t mind saying again, the difference is it’s much worse! It’s a function you can call in your flakes which takes a module and returns a shell derivation, and that’s it. devenv is a command line tool which does not require you to work with flakes, has many great subcommands like `search` and `up`, includes I-don’t-know-how-many option declarations contributed and maintained by I-don’t-know-how-many people (including myself), and is supported by a great company. `make-shell` has zero high-level option declarations, and I wasn’t planning on adding any, except in other flakes like [javascript-nix](https://github.com/nicknovitski/javascript-nix).

If you’re evaluating options for adopting nix for a project, my advice is you shouldn’t even consider `make-shell`. Maybe that’s something I should emphasize in [the why file](https://github.com/nicknovitski/make-shell/blob/e7d523d93f7b7e10758659fdec2e35265b58d7c6/WHY.md)…

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [August 4, 2024, 5:58pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/19 "2024-08-04T17:58:41Z")

</div>

I just released v1.2.0, which has a big not-breaking change: the deprecation of the `function` option and the addition of the `stdenv` option.

I think makes a little more sense; `function` always felt strange to me.

```nix
let
  thisEmitsAWarning = make-shell ({pkgs, ...}: { function = pkgs.mkShellNoCC; });
  useThisInstead = make-shell ({pkgs, ...}: { stdenv = pkgs.stdenvNoCC; });
in
  thisEmitsAWarning == useThisInstead

```

---

<div class="post-metadata">

### Author: ![nicknovitski](https://discourse.nixos.org/user_avatar/discourse.nixos.org/nicknovitski/32/11534_2.png) [@nicknovitski](https://discourse.nixos.org/u/nicknovitski)
#### Post date: [October 22, 2024, 2:16pm UTC](https://discourse.nixos.org/t/make-shell-a-modular-replacement-for-mkshell/48509/20 "2024-10-22T14:16:46Z")

</div>

I’ve released another shell module, [GitHub - nicknovitski/gcloud-nix: A shell module for adding the Google Cloud SDK](https://github.com/nicknovitski/gcloud-nix).

The [Google Cloud SDK](https://cloud.google.com/sdk/?hl=en) includes the `gcloud` command line tool, which has a `components` subcommand for installing a variety of other tools and packages. Some of those tools have separate packages in nixpkgs, but all of the components are also packaged _as components_ in nixpkgs, as an interesting interface for the package `google-cloud-sdk`:

```nix
pkgs.google-cloud-sdk.withExtraComponents [
  pkgs.google-cloud-sdk.components.kubectl
  pkgs.google-cloud-sdk.components.gke-gcloud-auth-plugin
  pkgs.google-cloud-sdk.components.skaffold
]

```

`gcloud-nix` lets me make this terser, and merges component definitions, so I can do something like this:

```nix
# gke.nix
{pkgs, ...}: {
  gcloud.enable = true;
  gcloud.extra-components = ["kubectl" "gke-gcloud-auth-plugin" "skaffold"];
}

# For most projects:
pkgs.make-shell {
  imports = [./gke.nix];
  # project-specific options
}

# For a project which also uses pubsub:
pkgs.make-shell ({pkgs, ...}: {
  imports = [./gke.nix];
  gcloud.extra-components = ["pubsub-emulator"]; # concatenated cleanly with gke.nix!
  packages = [pkgs.openjdk]; # necessary for the emulator to run
  shellHook = "gcloud beta emulators pubsub env-init"; # set environment variables so pubsub applications run in this shell will target the emulator
})

```

Relatedly, I’m also considering upstreaming an option to make-shell which I’ve found use for in a few projects: `interactiveShellHook`. It basically does this:

```nix
config.shellHook = lib.mkAfter ''
    if [[-n "''${_nix_direnv_force_reload:-}" || $- == *i*]]; then
      ${config.interactiveShellHook}
    fi
'';

```

The conditional is true when someone is interactively running `nix-shell` or `nix-develop`, or `nix-direnv-reload` from nix-direnv’s manual mode ([GitHub - nix-community/nix-direnv: A fast, persistent use\_nix/use\_flake implementation for direnv [maintainer=@Mic92 / @bbenne10]](https://github.com/nix-community/nix-direnv)). Otherwise it is false.

`mkAfter` means that this will be added to the end of `shellHook`, which made sense to me: conditionally evaluated code coming after code which is always evaluated.

It can be used for things like this:

```nix
pkgs.make-shell {
  env.AWS_PROFILE = "project-profile";
  packages = [pkgs.awscli2];
  interactiveShellHook = ''
    if ! aws configure list; the
      echo "Set your AWS access key id and secret access key! (Other values can be blank)"
      aws configure --profile "$AWS_PROFILE"
    fi
  '';
}

pkgs.make-shell {
  gcloud.enable = true;
  gcloud.extra-components = ["gke-gcloud-auth-plugin" "kubectl"];
  interactiveShellHook = "gcloud container clusters get-credentials myClusterName";
}

```
