Claude Code for Nix: Reproducible Environments, Flakes, and Development Shells — Claude Skills 360 Blog
Blog / Infrastructure / Claude Code for Nix: Reproducible Environments, Flakes, and Development Shells
Infrastructure

Claude Code for Nix: Reproducible Environments, Flakes, and Development Shells

Published: October 10, 2026
Read time: 8 min read
By: Claude Skills 360

Nix builds packages and environments reproducibly — given the same inputs, it always produces the same output. Nix flakes make this reproducibility shareable: your flake.nix pins exact versions of every dependency, and any developer running nix develop gets the exact same shell regardless of their OS or existing tooling. Claude Code writes flake configurations, explains the Nix expression language, and sets up development shells, CI environments, and NixOS modules.

CLAUDE.md for Nix Projects

## Nix Configuration
- Nix flakes enabled (nix.settings.experimental-features = "nix-command flakes")
- nixpkgs: follows input pinned in flake.lock — never use `<nixpkgs>`
- devShell: defined in flake.nix — all developers use `nix develop`
- Packages: prefer nixpkgs versions; custom derivations in ./nix/
- CI: nix build + nix flake check (runs all checks in the flake)
- NixOS modules: declared in ./modules/, imported in flake.nix
- Never use nix-env -i globally — all tooling in devShell or home-manager

Flake.nix for a Development Project

# flake.nix
{
  description = "Order management service";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    flake-utils.url = "github:numtide/flake-utils";
    
    # Rust toolchain pinning
    fenix = {
      url = "github:nix-community/fenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, flake-utils, fenix }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        
        # Pinned Rust toolchain (from rust-toolchain.toml)
        rustToolchain = fenix.packages.${system}.fromToolchainFile {
          file = ./rust-toolchain.toml;
          sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
        };
        
      in {
        # `nix develop` drops you here
        devShells.default = pkgs.mkShell {
          buildInputs = [
            # Language runtimes
            pkgs.nodejs_20
            pkgs.python311
            rustToolchain
            
            # Database tools
            pkgs.postgresql_16
            pkgs.redis
            
            # Build tools
            pkgs.gnumake
            pkgs.gcc
            pkgs.pkg-config
            
            # Dev tooling
            pkgs.git
            pkgs.jq
            pkgs.curl
            pkgs.httpie
            
            # Project-specific
            pkgs.nodePackages.typescript
            pkgs.nodePackages.prettier
          ];
          
          # Environment variables in the dev shell
          shellHook = ''
            export DATABASE_URL="postgresql://localhost/orders_dev"
            export REDIS_URL="redis://localhost:6379"
            
            # Start local services if not running
            if ! pg_isready -q; then
              echo "Starting PostgreSQL..."
              pg_ctl start -D ~/.postgres/data -l ~/.postgres/log
            fi
            
            echo "Dev environment ready. Run 'make dev' to start the server."
          '';
        };
        
        # `nix build` builds the application
        packages.default = pkgs.stdenv.mkDerivation {
          pname = "order-api";
          version = "0.1.0";
          src = ./.;
          
          buildInputs = [ pkgs.nodejs_20 ];
          
          buildPhase = ''
            npm ci
            npm run build
          '';
          
          installPhase = ''
            mkdir -p $out/lib/order-api
            cp -r dist $out/lib/order-api/
            cp package.json $out/lib/order-api/
            
            mkdir -p $out/bin
            cat > $out/bin/order-api << EOF
            #!/bin/sh
            exec ${pkgs.nodejs_20}/bin/node $out/lib/order-api/dist/server.js "\$@"
            EOF
            chmod +x $out/bin/order-api
          '';
        };
        
        # `nix flake check` runs all these
        checks = {
          # Run tests
          tests = pkgs.runCommand "tests" { buildInputs = [ pkgs.nodejs_20 ]; } ''
            cd ${self}
            npm ci
            npm test
            touch $out
          '';
          
          # Lint
          lint = pkgs.runCommand "lint" { buildInputs = [ pkgs.nodejs_20 ]; } ''
            cd ${self}
            npm run lint
            touch $out
          '';
        };
      }
    );
}

Custom Derivation (Package)

# nix/my-custom-tool.nix — package a Go binary not in nixpkgs
{ lib, buildGoModule, fetchFromGitHub }:

buildGoModule rec {
  pname = "my-codegen";
  version = "0.4.2";
  
  src = fetchFromGitHub {
    owner = "myorg";
    repo = "codegen";
    rev = "v${version}";
    sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };
  
  vendorHash = "sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=";
  
  meta = with lib; {
    description = "Code generator for myorg";
    license = licenses.mit;
    maintainers = [ maintainers.yourname ];
  };
}
# Import in flake.nix
let
  myCustomTool = pkgs.callPackage ./nix/my-custom-tool.nix {};
in pkgs.mkShell {
  buildInputs = [ myCustomTool ] ++ existingTools;
}

Home Manager Module

# home-manager/modules/development.nix
{ config, pkgs, lib, ... }:

{
  options.programs.devtools = {
    enable = lib.mkEnableOption "developer tools";
    extraPackages = lib.mkOption {
      type = lib.types.listOf lib.types.package;
      default = [];
      description = "Additional packages to install";
    };
  };
  
  config = lib.mkIf config.programs.devtools.enable {
    home.packages = with pkgs; [
      git
      gh
      neovim
      tmux
      ripgrep
      fd
      bat
      eza
      delta       # Better git diff
      lazygit
    ] ++ config.programs.devtools.extraPackages;
    
    programs.git = {
      enable = true;
      userName = "Your Name";
      userEmail = "[email protected]";
      extraConfig = {
        core.pager = "${pkgs.delta}/bin/delta";
        interactive.diffFilter = "${pkgs.delta}/bin/delta --color-only";
        push.autoSetupRemote = true;
        pull.rebase = true;
      };
    };
    
    programs.zsh = {
      enable = true;
      enableCompletion = true;
      initExtra = ''
        # Load direnv for per-directory env vars
        eval "$(${pkgs.direnv}/bin/direnv hook zsh)"
      '';
    };
  };
}

NixOS Module

# modules/order-api.nix — systemd service for the order API
{ config, lib, pkgs, ... }:

let
  cfg = config.services.order-api;
in {
  options.services.order-api = {
    enable = lib.mkEnableOption "order API service";
    
    port = lib.mkOption {
      type = lib.types.port;
      default = 3000;
      description = "Port to listen on";
    };
    
    environmentFile = lib.mkOption {
      type = lib.types.path;
      description = "Path to .env file with secrets";
    };
  };
  
  config = lib.mkIf cfg.enable {
    systemd.services.order-api = {
      description = "Order API Service";
      wantedBy = [ "multi-user.target" ];
      after = [ "postgresql.service" "redis.service" "network.target" ];
      
      environment = {
        PORT = toString cfg.port;
        NODE_ENV = "production";
      };
      
      serviceConfig = {
        Type = "simple";
        User = "order-api";
        Group = "order-api";
        
        ExecStart = "${pkgs.order-api}/bin/order-api";
        EnvironmentFile = cfg.environmentFile;
        
        # Security hardening
        NoNewPrivileges = true;
        PrivateTmp = true;
        ProtectSystem = "strict";
        ProtectHome = true;
        
        Restart = "on-failure";
        RestartSec = "5s";
      };
    };
    
    users.users.order-api = {
      isSystemUser = true;
      group = "order-api";
    };
    users.groups.order-api = {};
    
    networking.firewall.allowedTCPPorts = [ cfg.port ];
  };
}

direnv Integration

# .envrc — use in any project directory for auto-activation
# nix flake is loaded automatically when you cd into the directory
use flake

# Or for non-flake projects
use nix shell.nix
# Workflow
cd /path/to/project  # direnv activates the nix devShell automatically
# All tools from flake.nix are now in PATH

# First time: allow the .envrc
direnv allow

# Update the flake lock (pin to latest nixpkgs)
nix flake update

# Run all checks before commit
nix flake check

# Build the deployment artifact
nix build
./result/bin/order-api --version

For the CI/CD integration that uses nix flake check for reproducible test runs, see the zero-downtime deployments guide. For supply chain security, the supply chain security guide covers how Nix’s reproducibility complements SBOM generation. The Claude Skills 360 bundle includes Nix skill sets covering flake configuration, devShell setup, and NixOS module patterns. Start with the free tier to try Nix development environment generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free