Agent Skill
2/7/2026

node2nix

node2nix Skill

O
olafkfreund
10GitHub Stars
2Views
npx skills add olafkfreund/nixos_config

SKILL.md

Namenode2nix
Descriptionnode2nix Skill

name: node2nix version: 1.0 description: node2nix Skill

node2nix Skill

A specialized skill for converting NPM packages to Nix expressions using node2nix, enabling declarative and reproducible Node.js package management with Nix.

Skill Overview

Purpose: Provide comprehensive support for using node2nix to generate Nix expressions from NPM packages, handle dependencies, and integrate Node.js projects with Nix/NixOS.

Invoke When:

  • Converting npm projects to Nix packages
  • Creating Nix derivations for Node.js applications
  • Packaging Node.js tools for NixOS
  • Managing Node.js dependencies declaratively
  • Building reproducible Node.js applications
  • Troubleshooting node2nix issues
  • Setting up development environments for Node.js

Core Capabilities

1. Installation

Via Nix (Recommended)

# Install node2nix from nixpkgs
nix-env -f '<nixpkgs>' -iA nodePackages.node2nix

# Or with nix profile
nix profile install nixpkgs#nodePackages.node2nix

# Verify installation
node2nix --version

On NixOS

# /etc/nixos/configuration.nix
environment.systemPackages = with pkgs; [
  nodePackages.node2nix
];

Via home-manager

# home.nix
home.packages = with pkgs; [
  nodePackages.node2nix
];

Via npm (Alternative)

# Global installation
npm install -g node2nix

# Or use npx (no installation)
npx node2nix

2. Basic Usage

Quick Start - Generate from package.json

# Navigate to your Node.js project
cd my-nodejs-project

# Generate Nix expressions
node2nix

# This creates three files:
# - node-packages.nix (package definitions)
# - node-env.nix (build logic)
# - default.nix (composition expression)

Build the Package

# Build the package
nix-build -A package

# Result symlink points to build output
./result/bin/my-app

# Or install to profile
nix-env -f default.nix -iA package

Generated Files Explained

default.nix - Main entry point:

{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };
in
{
  package = nodePackages.package;
  shell = nodePackages.shell;
}

node-packages.nix - Package definitions:

# Generated by node2nix
# Contains all package definitions with dependencies
{
  # ... dependency definitions
  "my-package" = nodeEnv.buildNodePackage {
    name = "my-package";
    version = "1.0.0";
    src = ./.;
    dependencies = [ ... ];
    # ... build configuration
  };
}

node-env.nix - Build environment:

# Shared build logic for all packages
# Handles npm install, dependency linking, etc.
# Usually not modified directly

3. Lock File Support

Using package-lock.json (npm 5+)

# Generate with package-lock.json
node2nix -l package-lock.json

# Or explicitly
node2nix --lock package-lock.json

# Ensures exact dependency versions

Using npm-shrinkwrap.json

# Generate with shrinkwrap file
node2nix -l npm-shrinkwrap.json

Using yarn.lock

# Generate with yarn lock file
node2nix -l yarn.lock

# Note: Requires yarn support in node2nix

Why Use Lock Files?

  • Reproducibility: Exact same packages every time
  • Version Pinning: Lock specific dependency versions
  • Security: Prevent unexpected updates
  • Consistency: Same builds across machines

4. Development vs Production Dependencies

Production Mode (Default)

# Only install production dependencies
node2nix

# Explicit production mode
node2nix --production

Development Mode

# Include devDependencies
node2nix --development

# Useful for building developer tools
node2nix --development -i package.json

Example: Building a Tool with Dev Dependencies

# For tools like TypeScript compiler
node2nix --development

# Result includes devDependencies
nix-build -A package

5. Node.js Version Targeting

Default (Node.js 12+)

# Uses latest LTS Node.js
node2nix

Node.js 4.x Compatibility

# Enable Node.js 4.x mode
node2nix -4

# Equivalent to:
node2nix --nodejs-4

Node.js 6.x

node2nix -6

Node.js 8.x

node2nix -8

Custom Node.js Version in Generated Package

# Override Node.js version in default.nix
{ pkgs ? import <nixpkgs> {} }:

let
  nodejs = pkgs.nodejs_20;  # Use Node.js 20
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
    nodejs = nodejs;
  };
in
{
  package = nodePackages.package;
}

6. Input Files

Custom Input File

# Use custom package.json location
node2nix -i packages/backend/package.json

# Output to custom directory
node2nix -i package.json -o node-packages.nix

# Custom composition file
node2nix -c my-composition.nix

Multiple Packages

# Create a node-packages.json file
cat > node-packages.json <<EOF
[
  "express",
  "lodash",
  "axios"
]
EOF

# Generate from package list
node2nix -i node-packages.json

# Builds packages without package.json

Supplement File

# Add extra packages not in dependencies
cat > supplement.json <<EOF
{
  "global-tools": {
    "pm2": "^5.0.0",
    "nodemon": "^2.0.0"
  }
}
EOF

# Generate with supplements
node2nix --supplement-input supplement.json

7. Private Registries & Authentication

Private Registry

# Configure private registry
node2nix \
  --registry "https://registry.company.com" \
  --registry-auth-token "YOUR_AUTH_TOKEN"

# With scope
node2nix \
  --registry "https://registry.company.com" \
  --registry-scope "@mycompany"

Multiple Registries

# Different registries for different scopes
node2nix \
  --registry "https://public.npm.org" \
  --registry "https://private.company.com" \
  --registry-scope "@company" \
  --registry-auth-token "TOKEN"

Using .npmrc

# node2nix respects .npmrc settings
cat > .npmrc <<EOF
@mycompany:registry=https://registry.company.com/
//registry.company.com/:_authToken=YOUR_TOKEN
EOF

node2nix

Private Git Repositories

# Enable SSH for private git deps
node2nix --use-fetchgit-private

# For dependencies like:
# "my-lib": "git+ssh://git@github.com/company/lib.git"

8. Dependency Handling

Peer Dependencies

# Include peer dependencies
node2nix --include-peer-dependencies

# Useful for plugin systems

Strip Optional Dependencies

# Remove optional dependencies
node2nix --strip-optional-dependencies

# Helps when optional deps cause build failures

Bypass Cache

# Force fresh package metadata fetch
node2nix --bypass-cache

# Useful when registry data is stale

No Copy DevDependencies

# Don't copy devDependencies to store
node2nix --no-copy-node-env

# Reduces closure size

9. Override Mechanism

Basic Override

# default.nix
{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };
in
{
  package = nodePackages.package.override {
    # Add native dependencies
    buildInputs = with pkgs; [
      python3
      pkgs.cairo
      pkgs.pango
    ];

    # Skip npm install phase
    dontNpmInstall = true;

    # Custom build phase
    buildPhase = ''
      npm run custom-build
    '';
  };
}

Override Specific Package

{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = (import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  }).override {
    # Override for specific dependency
    "canvas" = oldAttrs: {
      buildInputs = oldAttrs.buildInputs ++ [
        pkgs.cairo
        pkgs.pango
        pkgs.giflib
      ];

      preInstall = ''
        export CANVAS_NO_REBUILD=1
      '';
    };

    # Fix bcrypt native module
    "bcrypt" = oldAttrs: {
      buildInputs = [ pkgs.python3 ];
    };
  };
in
{
  package = nodePackages.package;
}

Global Override

# Override all packages
{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;

    # Global overrides
    globalBuildInputs = with pkgs; [
      python3
      pkgs.nodejs.libv8
    ];
  };
in
nodePackages

10. Development Shell

Generate Shell Environment

# Generate with shell support
node2nix

# Enter development shell
nix-shell -A shell

# Now you can:
# - Modify source code
# - Run npm scripts
# - Test without rebuilding

Enhanced Shell

# shell.nix
{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };
in
nodePackages.shell.override {
  buildInputs = with pkgs; [
    # Additional development tools
    nodejs_20
    nodePackages.typescript
    nodePackages.eslint
    nodePackages.prettier

    # Native dependencies for development
    python3
    cairo
    pango
  ];

  shellHook = ''
    echo "Node.js development environment"
    echo "Node version: $(node --version)"
    echo "npm version: $(npm --version)"

    # Set up node_modules symlink
    [ -d node_modules ] || ln -s $NODE_PATH node_modules

    # Custom environment variables
    export NODE_ENV=development
    export DEBUG=*
  '';
}

Using the Shell

# Enter shell
nix-shell

# Run development server
npm run dev

# Run tests
npm test

# Build
npm run build

11. Common Patterns

Pattern 1: Simple CLI Tool

# Project structure:
# my-cli/
# ├── package.json
# ├── package-lock.json
# └── bin/
#     └── my-cli.js

# Generate Nix expressions
cd my-cli
node2nix -l package-lock.json

# Build
nix-build -A package

# Test
./result/bin/my-cli --version

# Install
nix-env -f default.nix -iA package

Pattern 2: Web Application

# Express.js app with dependencies
node2nix -l package-lock.json

# Custom default.nix for systemd service
{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };

  app = nodePackages.package;
in
{
  inherit app;

  # Systemd service
  service = pkgs.writeTextFile {
    name = "my-app.service";
    text = ''
      [Unit]
      Description=My Node.js App
      After=network.target

      [Service]
      Type=simple
      ExecStart=${app}/bin/my-app
      Restart=on-failure
      Environment="NODE_ENV=production"

      [Install]
      WantedBy=multi-user.target
    '';
  };
}

Pattern 3: Monorepo Package

# Workspace project
# monorepo/
# ├── package.json
# ├── packages/
# │   ├── app/
# │   │   └── package.json
# │   └── lib/
# │       └── package.json

# Generate from root
node2nix -l package-lock.json

# Or generate per package
cd packages/app
node2nix -l ../../package-lock.json

Pattern 4: TypeScript Project

# Include dev dependencies for TypeScript
node2nix --development -l package-lock.json

# Build includes TypeScript compilation
{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };
in
{
  package = nodePackages.package.override {
    buildPhase = ''
      # Compile TypeScript
      npm run build
    '';

    installPhase = ''
      mkdir -p $out/bin
      cp -r dist/* $out/

      # Create wrapper
      makeWrapper ${pkgs.nodejs}/bin/node $out/bin/my-app \
        --add-flags "$out/index.js"
    '';
  };
}

Pattern 5: Electron App

# Electron requires development dependencies
node2nix --development -l package-lock.json
{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };
in
{
  package = nodePackages.package.override {
    buildInputs = with pkgs; [
      # Electron dependencies
      xorg.libX11
      xorg.libXtst
      gtk3
      nss
      nspr
      alsa-lib
      cups
      dbus
      atk
      cairo
      pango
      gdk-pixbuf
      gtk3
    ];

    # Don't rebuild native modules
    ELECTRON_SKIP_BINARY_DOWNLOAD = "1";
  };
}

12. NixOS Integration

Package in NixOS Configuration

# /etc/nixos/configuration.nix
{ config, pkgs, ... }:

let
  myNodeApp = import /path/to/my-app {
    inherit pkgs;
  };
in
{
  environment.systemPackages = [
    myNodeApp.package
  ];

  # Or as a systemd service
  systemd.services.my-node-app = {
    description = "My Node.js Application";
    after = [ "network.target" ];
    wantedBy = [ "multi-user.target" ];

    serviceConfig = {
      ExecStart = "${myNodeApp.package}/bin/my-app";
      Restart = "on-failure";
      User = "nodejs";

      # Security hardening
      DynamicUser = true;
      ProtectSystem = "strict";
      NoNewPrivileges = true;
      PrivateTmp = true;
    };

    environment = {
      NODE_ENV = "production";
      PORT = "3000";
    };
  };
}

Module for Node.js App

# modules/my-app.nix
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.my-app;

  myApp = import ../my-app {
    inherit pkgs;
  };
in
{
  options.services.my-app = {
    enable = mkEnableOption "My Node.js App";

    port = mkOption {
      type = types.int;
      default = 3000;
      description = "Port to listen on";
    };

    environment = mkOption {
      type = types.attrsOf types.str;
      default = {};
      description = "Environment variables";
    };
  };

  config = mkIf cfg.enable {
    systemd.services.my-app = {
      description = "My Node.js Application";
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];

      serviceConfig = {
        ExecStart = "${myApp.package}/bin/my-app";
        Restart = "on-failure";
        DynamicUser = true;
      };

      environment = cfg.environment // {
        NODE_ENV = "production";
        PORT = toString cfg.port;
      };
    };

    networking.firewall.allowedTCPPorts = [ cfg.port ];
  };
}

13. Flake Integration

flake.nix for Node.js Project

{
  description = "My Node.js Application";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};

        nodePackages = import ./node-packages.nix {
          inherit pkgs;
          inherit (pkgs) system fetchurl fetchgit stdenv lib;
        };
      in
      {
        packages = {
          default = nodePackages.package;
          my-app = nodePackages.package;
        };

        apps.default = {
          type = "app";
          program = "${nodePackages.package}/bin/my-app";
        };

        devShells.default = nodePackages.shell.override {
          buildInputs = with pkgs; [
            nodejs_20
            nodePackages.typescript
          ];
        };
      }
    );
}

Use Flake

# Build
nix build

# Run
nix run

# Development shell
nix develop

# Update dependencies
node2nix -l package-lock.json
nix flake lock --update-input nixpkgs

14. Troubleshooting

Issue 1: Native Module Build Failures

Problem: Package with native dependencies fails to build

Solution:

{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };
in
{
  package = nodePackages.package.override {
    buildInputs = with pkgs; [
      python3  # For node-gyp
      pkgs.nodejs.libv8

      # Common native deps
      cairo
      pango
      giflib
      libjpeg
      libpng
    ];

    # Environment for native builds
    NIX_CFLAGS_COMPILE = "-I${pkgs.cairo.dev}/include/cairo";
    NIX_LDFLAGS = "-L${pkgs.cairo}/lib";
  };
}

Issue 2: Plugin Discovery Failures

Problem: Application can't find plugins/modules

Solution:

# In development shell
nix-shell -A shell

# Create node_modules symlink
ln -s $NODE_PATH node_modules

# Or in shell.nix:
shellHook = ''
  [ -d node_modules ] || ln -s $NODE_PATH node_modules
'';

Issue 3: Package Not Found in Registry

Problem: node2nix can't fetch package

Solution:

# Bypass cache
node2nix --bypass-cache

# Or update lock file
npm install
npm update
node2nix -l package-lock.json

Issue 4: Peer Dependency Issues

Problem: Missing peer dependencies

Solution:

# Include peer dependencies
node2nix --include-peer-dependencies -l package-lock.json

Issue 5: Private Git Repository Access

Problem: Can't fetch from private git repos

Solution:

# Enable SSH for git
node2nix --use-fetchgit-private

# Ensure SSH keys are configured
# For declarative builds, use fetchgit with SSH URL override

Issue 6: Large Closure Size

Problem: Generated package has large closure

Solution:

# Production mode (no dev deps)
node2nix --production -l package-lock.json

# Strip optional dependencies
node2nix --strip-optional-dependencies
# Remove unnecessary build dependencies
{
  package = nodePackages.package.override {
    dontNpmInstall = true;

    installPhase = ''
      # Install only what's needed
      mkdir -p $out
      cp -r dist $out/
      cp package.json $out/
    '';
  };
}

15. Advanced Usage

Custom Composition

# Generate with custom composition file
node2nix -c my-composition.nix

# Allows custom package structure

Patch Packages

{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };
in
{
  package = nodePackages.package.overrideAttrs (oldAttrs: {
    patches = [
      ./patches/fix-vulnerability.patch
    ];

    postPatch = ''
      # Patch package.json
      substituteInPlace package.json \
        --replace "old-version" "new-version"
    '';
  });
}

Multi-Platform Support

{ pkgs ? import <nixpkgs> {} }:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };

  # Platform-specific overrides
  package = if pkgs.stdenv.isDarwin
    then nodePackages.package.override {
      buildInputs = with pkgs.darwin.apple_sdk.frameworks; [
        CoreServices
        Foundation
      ];
    }
    else nodePackages.package;
in
{
  inherit package;
}

Development vs Production Builds

# default.nix
{ pkgs ? import <nixpkgs> {}
, production ? true
}:

let
  nodePackages = import ./node-packages.nix {
    inherit pkgs;
    inherit (pkgs) system fetchurl fetchgit stdenv lib;
  };
in
{
  package = nodePackages.package.override {
    buildPhase = if production
      then ''
        export NODE_ENV=production
        npm run build
      ''
      else ''
        export NODE_ENV=development
        npm run build:dev
      '';
  };
}

Best Practices

DO ✅

  1. Always use lock files

    node2nix -l package-lock.json
    
  2. Version control generated files

    git add node-packages.nix node-env.nix default.nix
    git commit -m "Add Nix expressions for Node.js project"
    
  3. Use production mode for deployments

    node2nix --production -l package-lock.json
    
  4. Override packages with native deps

    buildInputs = [ pkgs.python3 pkgs.cairo ];
    
  5. Test in nix-shell before building

    nix-shell -A shell
    npm test
    
  6. Pin nixpkgs version

    pkgs ? import (fetchTarball {
      url = "https://github.com/NixOS/nixpkgs/archive/COMMIT.tar.gz";
      sha256 = "...";
    }) {}
    
  7. Document overrides and customizations

    # Override for canvas - requires Cairo
    buildInputs = [ pkgs.cairo ];
    
  8. Use flakes for modern projects

    inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    
  9. Separate development and production configs

    # dev: node2nix --development
    # prod: node2nix --production
    
  10. Keep node2nix up to date

    nix-env -u nodePackages.node2nix
    

DON'T ❌

  1. Don't commit node_modules

    # .gitignore
    node_modules/
    
  2. Don't skip lock files

    # ❌ Bad
    node2nix
    
    # ✅ Good
    node2nix -l package-lock.json
    
  3. Don't ignore build failures silently

    # ❌ Bad - hides issues
    dontBuild = true;
    
    # ✅ Good - fix the issue
    buildInputs = [ requiredDeps ];
    
  4. Don't hardcode paths

    # ❌ Bad
    "/usr/bin/node"
    
    # ✅ Good
    "${pkgs.nodejs}/bin/node"
    
  5. Don't mix npm and nix package management

    # ❌ Don't run npm install manually
    # ✅ Let Nix handle it
    
  6. Don't commit secrets

    # Never commit tokens or auth
    # Use environment variables or secrets management
    
  7. Don't skip regeneration after updates

    # After npm install/update:
    node2nix -l package-lock.json
    

Command Reference

# Basic usage
node2nix                                    # Generate from package.json
node2nix -l package-lock.json              # Use lock file
node2nix --development                      # Include devDependencies
node2nix -i packages.json                  # Custom input file

# Node.js versions
node2nix -4                                 # Node.js 4.x
node2nix -6                                 # Node.js 6.x
node2nix -8                                 # Node.js 8.x

# Registry configuration
node2nix --registry URL                     # Custom registry
node2nix --registry-auth-token TOKEN        # Auth token
node2nix --registry-scope SCOPE            # Scoped packages

# Dependency handling
node2nix --include-peer-dependencies       # Include peers
node2nix --strip-optional-dependencies     # Remove optional
node2nix --bypass-cache                    # Force fresh fetch

# Advanced
node2nix --use-fetchgit-private            # Private git repos
node2nix --supplement-input FILE           # Additional packages
node2nix --no-copy-node-env                # Smaller closure

# Output control
node2nix -o node-packages.nix              # Custom output
node2nix -c composition.nix                # Custom composition
node2nix -e node-env.nix                   # Custom environment

# Help
node2nix --help                            # Show help
node2nix --version                         # Show version

Success Metrics

  • Reproducible Builds: Same package.json → same output
  • Declarative: Everything in Nix expressions
  • Version Controlled: Generated files tracked in git
  • Integrated: Works with NixOS, flakes, home-manager
  • Tested: Builds successfully in clean environment
  • Documented: Overrides and customizations explained

Ready to convert NPM packages to Nix with node2nix! 📦

Skills Info
Original Name:node2nixAuthor:olafkfreund