tracking for shynet analytics when Javascript is disabled Recreating development environment with Nix Flake — Hugo Sum
Hugo Sum

Recreating development environment with Nix Flake

In this post, I will show you how to define and recreate development environment of your projects anywhere using Nix Flake.

Create your development environment with nix develop

You can define the development environment of your project in Nix Flake under devShells with pkgs.mkShell, and a shell based on that environment can be created with nix develop .#<environment-name>. For the packages you want to include in this environment, you can define them in nativeBuildInputs, and they will be loaded through the PATH in the shell automatically.

The following example is a flake that install kustomize in devShells.default. flake-parts is used to simplify configuration, so you can define devShells.<name> instead of devShells.<system>.<name> for each system.

flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
    flake-parts.url = "github:hercules-ci/flake-parts";
  };

  outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [ "x86_64-linux" ];

      perSystem = { config, pkgs, system, ... }:
        let
          pkgs = import nixpkgs {
            inherit system;
            config.allowUnfree = true;
            overlays = [ ];
          };
        in {
          devShells.default = pkgs.mkShell {
            nativeBuildInputs = with pkgs; [
              kustomize
            ];
          };
        };
    };
}

If you want to define environment variables in this environment or trigger some actions when entering it, for example start a database process in background, you can define those commands in shellHook.

flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
    flake-parts.url = "github:hercules-ci/flake-parts";
  };

  outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [ "x86_64-linux" ];

      perSystem = { config, pkgs, system, ... }:
        let
          pkgs = import nixpkgs {
            inherit system;
            config.allowUnfree = true;
            overlays = [ ];
          };
        in {
          devShells.default = pkgs.mkShell {
            nativeBuildInputs = with pkgs; [
              kustomize
            ];

            shellHook = ''
              set -a;
              export FOO="bar";
              set +a;
            '';
          };
        };
    };
}

You might have also seen snippets of pkgs.mkShell that defines what packages to use through buildInputs or packages. buildInputs is interchangeable with nativeBuildInputs, unless you are cross compiling, as packages defined in it are for the foreign platform that your compilation is targeting, and packages defined in nativeBuildInputs are for the native platform where the compilation will happen. packages is just an alias argument for nativeBuildInputs, as it will be merged with the nativeBuildInputs at the end.

Under the hood, pkgs.mkShell creates a derivation that only has a build phrase, which does not do anything on its own, and it is meant to be used only with the old nix-shell command, that is wrapped by nix develop.

Automate environment creation with direnv

Instead of running nix develop manually every time, what if there is a tool that can automate it for you, whenever you navigate into that project? direnv is a tool that does exactly that. With its Nix Flake integration, nix-direnv, it would call nix develop for you when you cd into a project, and the PATH and environment variables in your current shell will be updated temporarily, until you navigate away from that project. This is a small but important improvement for me, as I don’t have to use a bare-boned bash shell and I can continue to enjoy the custom prompt and key bindings in my current shell profile.

There are a few ways to install nix-direnv in your system. Assuming you are using Home Manager and you are using zsh as your shell, you would create a module as the following. Similar configuration exists for bash and fish as well.

direnv.nix
{ inputs, lib, config, pkgs, system, isDarwin, ... }: {
  programs.zsh = {
    enable = true;
  };
  programs.direnv.enable = true;
  programs.direnv.enableZshIntegration = true;
  programs.direnv.nix-direnv.enable = true;
}

Then in the project you want to enable nix-direnv support, you need to create a file called .envrc with use flake.

.envrc
use flake

Instead of the default devShells, you can define and run a specific devShells. In the following example, we instruct direnv to run devShells.<system>.ci automatically for us.

.envrc
use flake .#ci

Finally, you will need to run direnv allow once at the project level to allow direnv to do its magic. The next time you navigate into this project, your development environment will be recreated automatically by direnv.

Reproduce your development environment anywhere with Docker

Since our development environment is defined in Nix Flake, we can reproduce our environment on any machine with Nix Flake support, including a Docker image. The Nix community provides a Docker image for running Nix Flake, and we can recreate our project environment and execute commands in it.

I found it very useful to bring my environment from Nix Flake into a CI/CD pipeline through Docker. Not only the version of packages I get are identical with what I have locally, but also I don’t need to define the installation process for each of them again. Everything is controlled in the Nix Flake.

Since we cannot interact with a shell started by nix develop in a pipeline, we have to pass the command and arguments that we want to execute to nix develop as nix develop -c <command> [...args]. The following example is a task for Tekton pipeline that executes kustomize build --enable-helm ./final in an environment created by nix develop.

pipeline.yaml
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: generate-manifest
spec:
  steps:
  - image: docker.io/nixpkgs/nix-flakes:nixos-24.11
    name: generate-manifest
    script: |
      nix develop -c kustomize build --enable-helm ./final;

Hugo Sum

A Hongkonger living in the UK. The only thing I know is there is so much I don't know.

Enjoy reading my blog?

Subscribe to a monthly newsletter and join my adventure on software development.

© 2025 Hugo Sum. All Rights Reserved.