← All articles
DEVELOPMENT ENVIRONMENTS Devenv: Reproducible Development Environments with Nix 2026-02-15 · 9 min read · devenv · nix · development-environments

Devenv: Reproducible Development Environments with Nix

Development Environments 2026-02-15 · 9 min read devenv nix development-environments reproducibility developer-experience containers

Devenv: Reproducible Development Environments with Nix

Devenv Nix logo

Every developer has lived this experience: you clone a repository, follow the README, and spend the next two hours installing the right version of Python, the correct PostgreSQL release, a specific version of protoc, and three system libraries that the project silently depends on. The README says "works on my machine" and it does -- on the original author's machine, configured exactly the way they had it eighteen months ago.

Devenv solves this by letting you declare your entire development environment in a single file. It uses Nix under the hood -- the most powerful package manager ever built -- but wraps it in an approachable configuration format that does not require you to learn the Nix language. You declare which packages you need, which services to run, which environment variables to set, and which scripts to expose. When a teammate runs devenv shell, they get an identical environment regardless of whether they are on macOS, Linux, or NixOS.

Why Devenv?

The development environment problem has several attempted solutions, each with significant trade-offs.

Docker dev containers give you reproducibility but at a cost: file system mounts are slow on macOS, GPU passthrough is painful, and the feedback loop for changing dependencies requires rebuilding layers. You are developing inside a virtualized Linux environment that does not match your host OS.

Version managers (nvm, pyenv, rbenv) solve one language at a time. A real project needs Node, Python, PostgreSQL, Redis, and a dozen CLI tools. Coordinating six different version managers is fragile.

Nix flakes give you everything but require learning the Nix language, which has a steep learning curve. Writing a proper flake with devShells, overlays, and nixpkgs pinning is not something most developers want to do on day one.

Devbox by Jetify is the closest competitor. It also wraps Nix in a simpler interface. Devenv differs by offering integrated process management, service definitions (databases, queues), pre-commit hook support, language-specific module systems, and deeper Nix integration for those who want to drop down a level.

Devenv hits the sweet spot: Nix's power with a configuration experience closer to Docker Compose.

Installation

Devenv requires the Nix package manager. If you do not have Nix installed, Devenv's installer handles it:

# Install Devenv (installs Nix automatically if needed)
curl -fsSL https://devenv.sh/install.sh | bash

If you already have Nix with flakes enabled:

# Using Nix directly
nix profile install --accept-flake-config github:cachix/devenv/latest

Verify the installation:

devenv version

Cachix Binary Cache

Devenv uses Cachix to serve pre-built binaries so you do not compile everything from source. The installer configures this automatically. If you are on a team, you can set up a private Cachix cache to share builds:

cachix use your-team-cache

Your First devenv.nix

Initialize a project:

cd your-project
devenv init

This creates two files: devenv.nix (your environment definition) and devenv.yaml (input sources). The devenv.nix file is where you spend most of your time:

{ pkgs, ... }:

{
  # Packages available in the environment
  packages = [
    pkgs.git
    pkgs.curl
    pkgs.jq
  ];

  # Environment variables
  env.DATABASE_URL = "postgresql://localhost:5432/myapp";
  env.RUST_LOG = "debug";

  # Shell hook -- runs when entering the environment
  enterShell = ''
    echo "Welcome to the project dev environment"
    echo "Node: $(node --version)"
    echo "Python: $(python --version)"
  '';
}

Enter the environment:

devenv shell

This drops you into a shell with all declared packages available on your PATH, environment variables set, and the shell hook executed. When you exit, everything is gone -- no global pollution.

Language Support

Devenv has first-class modules for most popular languages. These handle not just the runtime but also package managers, build tools, and language servers.

Python Stack

{ pkgs, ... }:

{
  languages.python = {
    enable = true;
    version = "3.12";

    # Virtual environment managed by Devenv
    venv.enable = true;
    venv.requirements = ./requirements.txt;
  };

  packages = [
    pkgs.ruff        # Linter
    pkgs.black       # Formatter
  ];
}

Node.js Stack

{ pkgs, ... }:

{
  languages.javascript = {
    enable = true;
    package = pkgs.nodejs_22;

    # Or use Bun instead
    # bun.enable = true;
  };

  # TypeScript support
  languages.typescript.enable = true;

  packages = [
    pkgs.nodePackages.pnpm
  ];
}

Rust Stack

{ pkgs, ... }:

{
  languages.rust = {
    enable = true;
    channel = "stable";  # or "nightly"

    # Components to include
    components = [
      "rustc"
      "cargo"
      "clippy"
      "rustfmt"
      "rust-analyzer"
    ];
  };

  packages = [
    pkgs.pkg-config
    pkgs.openssl
  ];
}

Multi-Language Projects

Real projects often span multiple languages. Devenv handles this naturally:

{ pkgs, ... }:

{
  languages.python = {
    enable = true;
    version = "3.12";
  };

  languages.javascript = {
    enable = true;
    package = pkgs.nodejs_22;
  };

  languages.rust.enable = true;

  packages = [
    pkgs.protobuf      # For gRPC definitions
    pkgs.grpcurl        # gRPC testing
    pkgs.docker-compose # Container orchestration
  ];
}

Process Management

Devenv includes a built-in process manager powered by process-compose. This replaces the need for tools like Foreman, Overmind, or custom Makefiles that start multiple services:

{ pkgs, ... }:

{
  processes = {
    backend.exec = "cd backend && cargo watch -x run";
    frontend.exec = "cd frontend && npm run dev";
    worker.exec = "cd worker && python celery_worker.py";
    tailwind.exec = "npx tailwindcss -i input.css -o output.css --watch";
  };
}

Start all processes:

devenv up

This launches all processes with a TUI that shows logs for each service, supports restarting individual processes, and handles signal propagation for clean shutdown.

Services: Databases and Infrastructure

One of Devenv's strongest features is integrated service management. Instead of asking developers to install PostgreSQL globally or run Docker containers separately, you declare services directly:

PostgreSQL

{ pkgs, ... }:

{
  services.postgres = {
    enable = true;
    package = pkgs.postgresql_16;
    listen_addresses = "127.0.0.1";
    port = 5432;

    initialDatabases = [
      { name = "myapp_dev"; }
      { name = "myapp_test"; }
    ];

    initialScript = ''
      CREATE USER myapp WITH PASSWORD 'dev_password';
      GRANT ALL PRIVILEGES ON DATABASE myapp_dev TO myapp;
    '';
  };
}

Redis

{
  services.redis = {
    enable = true;
    port = 6379;
  };
}

Multiple Services Together

{ pkgs, ... }:

{
  services.postgres = {
    enable = true;
    package = pkgs.postgresql_16;
    initialDatabases = [{ name = "app"; }];
  };

  services.redis.enable = true;

  services.minio = {
    enable = true;
    buckets = ["uploads" "backups"];
  };

  services.mailpit = {
    enable = true;  # Local email testing
  };

  services.rabbitmq.enable = true;
}

When you run devenv up, all services start automatically with data directories inside .devenv/state/. No global installation, no port conflicts between projects, no leftover data from other projects.

Pre-Commit Hooks

Devenv integrates directly with the pre-commit framework. Instead of maintaining a separate .pre-commit-config.yaml, you declare hooks in your devenv.nix:

{ pkgs, ... }:

{
  pre-commit.hooks = {
    # Formatting
    nixpkgs-fmt.enable = true;
    prettier.enable = true;
    black.enable = true;
    rustfmt.enable = true;

    # Linting
    clippy.enable = true;
    ruff.enable = true;
    shellcheck.enable = true;

    # Security
    detect-private-key.enable = true;

    # Custom hook
    my-check = {
      enable = true;
      entry = "${pkgs.bash}/bin/bash -c 'echo checking...'";
      files = "\\.rs$";
      language = "system";
    };
  };
}

The hooks are installed automatically when entering the shell. No separate pre-commit install step needed.

Scripts and Tasks

Define project-specific scripts that are available as commands in the shell:

{ pkgs, ... }:

{
  scripts = {
    db-reset.exec = ''
      dropdb --if-exists myapp_dev
      createdb myapp_dev
      python manage.py migrate
      python manage.py seed
      echo "Database reset complete"
    '';

    lint.exec = ''
      ruff check .
      mypy src/
      prettier --check "**/*.{ts,tsx}"
    '';

    deploy.exec = ''
      echo "Deploying to $DEPLOY_ENV..."
      docker build -t myapp .
      docker push registry.example.com/myapp:latest
    '';
  };
}

These scripts are available as commands immediately:

$ devenv shell
$ db-reset
Database reset complete
$ lint
All checks passed!

Comparison with Alternatives

Feature Devenv Devbox Docker Dev Containers Nix Flakes
Nix-powered Yes Yes No Yes
Learning curve Low Low Medium High
Service management Built-in Via plugins Docker Compose Manual
Process manager Built-in Via plugins Manual Manual
Pre-commit hooks Built-in No Manual Manual
Language modules Rich Basic N/A Manual
macOS performance Native Native Slow (VM) Native
Team sharing devenv.nix in repo devbox.json in repo .devcontainer in repo flake.nix in repo
Escape hatch to Nix Full Limited N/A N/A (is Nix)
Container support devenv container Yes Native Manual
Caching Cachix Jetify Cloud Docker layers Cachix

The devenv.yaml File

While devenv.nix defines your environment, devenv.yaml defines where Nix packages come from:

inputs:
  nixpkgs:
    url: github:NixOS/nixpkgs/nixpkgs-unstable
  nixpkgs-stable:
    url: github:NixOS/nixpkgs/nixos-24.11

You can pin specific revisions for complete reproducibility:

inputs:
  nixpkgs:
    url: github:NixOS/nixpkgs/nixpkgs-unstable
    override:
      rev: abc123def456  # Pin to exact commit

Use packages from different channels:

{ pkgs, inputs, ... }:

let
  pkgs-stable = inputs.nixpkgs-stable.legacyPackages.${pkgs.system};
in {
  packages = [
    pkgs.nodejs_22          # Latest from unstable
    pkgs-stable.postgresql  # Stable PostgreSQL
  ];
}

Container Generation

Devenv can generate OCI containers from your environment definition, bridging the gap between development and production:

# Build a container image
devenv container build

# Build and load into Docker
devenv container run

Configure the container in devenv.nix:

{ pkgs, ... }:

{
  containers.app = {
    name = "myapp";
    entrypoint = ["python" "-m" "myapp"];

    copyToRoot = [
      ./src
      ./config
    ];
  };
}

This produces minimal container images because Nix knows exactly which dependencies your application needs -- nothing more.

Testing Environments

Devenv supports defining separate test configurations:

{ pkgs, ... }:

{
  env.DATABASE_URL = "postgresql://localhost:5432/myapp_dev";

  # Override for testing
  enterTest = ''
    export DATABASE_URL="postgresql://localhost:5432/myapp_test"
    echo "Running in test mode"
  '';
}

Run tests with the test environment:

devenv test

Direnv Integration

For the best developer experience, integrate Devenv with direnv so that your environment activates automatically when you cd into the project directory:

# Install direnv
# (on macOS: brew install direnv)
# (on Linux: your package manager)

# Create .envrc in your project root
echo "use devenv" > .envrc

# Allow direnv to load it
direnv allow

Now every time you enter the project directory, your shell automatically has the correct tools, environment variables, and PATH entries. When you leave, they disappear. No manual devenv shell required.

Real-World Configuration: Full-Stack Web Application

Here is a complete devenv.nix for a typical full-stack application with a Python backend, React frontend, PostgreSQL database, and Redis cache:

{ pkgs, ... }:

{
  # Languages
  languages.python = {
    enable = true;
    version = "3.12";
    venv.enable = true;
    venv.requirements = ./requirements.txt;
  };

  languages.javascript = {
    enable = true;
    package = pkgs.nodejs_22;
  };

  # System packages
  packages = [
    pkgs.pnpm
    pkgs.ruff
    pkgs.just   # Task runner
    pkgs.httpie # API testing
  ];

  # Environment variables
  env.DJANGO_SETTINGS_MODULE = "config.settings.development";
  env.SECRET_KEY = "dev-only-not-for-production";

  # Services
  services.postgres = {
    enable = true;
    package = pkgs.postgresql_16;
    initialDatabases = [
      { name = "webapp_dev"; }
      { name = "webapp_test"; }
    ];
  };

  services.redis.enable = true;

  # Processes
  processes = {
    backend.exec = "python manage.py runserver 0.0.0.0:8000";
    frontend.exec = "cd frontend && pnpm dev";
    worker.exec = "celery -A config worker -l INFO";
  };

  # Pre-commit hooks
  pre-commit.hooks = {
    ruff.enable = true;
    prettier.enable = true;
    detect-private-key.enable = true;
  };

  # Scripts
  scripts = {
    setup.exec = ''
      pnpm install --prefix frontend
      python manage.py migrate
      echo "Setup complete. Run 'devenv up' to start all services."
    '';

    db-seed.exec = "python manage.py loaddata fixtures/*.json";
  };

  # Shell hook
  enterShell = ''
    echo ""
    echo "Webapp Development Environment"
    echo "  Python:     $(python --version)"
    echo "  Node:       $(node --version)"
    echo "  PostgreSQL: $(postgres --version)"
    echo ""
    echo "Commands: setup, db-seed, devenv up"
  '';
}

A new developer joining the team runs three commands:

git clone [email protected]:your-org/webapp.git
cd webapp
devenv shell
setup
devenv up

Five minutes later, they have a fully running application with database, cache, backend, frontend, and worker -- all at the correct versions, all isolated from their global system.

When to Choose Devenv

Devenv is the right choice when your team struggles with "works on my machine" problems, when onboarding a new developer takes more than thirty minutes, when you need reproducible environments across macOS and Linux, or when your project depends on system-level tools (databases, compilers, native libraries) beyond what a single language's package manager provides.

If your project is a single-language application with no system dependencies, a version manager might be enough. If your team is already deep in the Nix ecosystem, raw flakes give you more control. If you need Windows support, Docker dev containers are currently the better option. But for the majority of teams building multi-component applications on macOS and Linux, Devenv offers the best combination of power and usability available today.