How to Use Devenv: The Beginner's Guide (First Environment, the Languages Lesson, Services, and the direnv Arc)
CodexFuse Team · 2026-11-06T16:24:47.755Z · 19 min read min read
What Is Devenv?
Devenv is a fast, declarative, reproducible developer-environment tool built on Nix, and learning how to use devenv means learning to describe your whole environment in one file. One devenv.nix manifest declares your languages, services, tasks, and environment variables, and Nix builds the exact same environment on every machine that runs it. As of November 2026, the current release is devenv v2.4.0, published 2026-09-24, and this devenv tutorial pins every command to that release.
The mental model is simple: one file, one environment, everywhere. Your editor, your language runtimes, your database, and your scripts all come from the same manifest, so a teammate who clones the repo gets the same setup you have. If you already pin tool versions, the Mise beginner guide covers that lane, because devenv provisions whole environments, not just tool versions.
Devenv is free and open source under the Apache License 2.0, and there is no paid tier to quote. It runs on Linux, macOS, and WSL2. The project is maintained by Cachix, the team behind the Cachix binary cache, and the repository had 7,807 stars and 586 forks when this guide was written.
The problem devenv solves is the one every team knows: it works on my machine, and nowhere else. A laptop with a newer Python, a missing system library, or a database on a different port produces bugs that only exist on one machine. One manifest removes that whole class of bug, because the environment is code, and code is the same everywhere it runs.
- Install Nix.
- Install devenv 2.4.0.
- Run devenv init in your project.
- Add a language to devenv.nix.
- Add a service to devenv.nix.
- Enter the environment with devenv shell.
- Start your services with devenv up.
How Do I Install Devenv?
Install Nix first, then install devenv with the newcomers command the official getting-started guide leads with. On Linux, macOS, or WSL2, the Nix installer is one command, and devenv installs from nixpkgs-unstable after that. Devenv does not run natively on Windows, so Windows users must use WSL2.
sh <(curl -L https://nixos.org/nix/install) --daemon
nix-env --install --attr devenv -f https://github.com/NixOS/nixpkgs/tarball/nixpkgs-unstable
If you already use Nix profiles with experimental flags enabled, the homepage summary shows nix profile install nixpkgs#devenv as the alternative route. Pick one route and stick with it, because mixing install methods is the most common way to end up with two devenv binaries. Check the installed version with devenv --version and confirm it reports 2.4.0 before you continue.
nix profile install nixpkgs#devenv
devenv --version
Nix talks to the GitHub API heavily, so the official guide recommends a GitHub token to avoid rate limiting. Create a token with no extra permissions and add it to your Nix configuration file, and the GitHub beginner guide covers the token habits around that step. The guide also shows how to enable the Cachix binary cache with cachix use devenv, which speeds up installs.
access-tokens = github.com=<GITHUB_TOKEN>
Secrets stay out of the environment file: SecretSpec reads them from your keyring, a dotenv file, 1Password, or environment variables, and devenv 2.2 upgraded it to version 0.17. Declare them in secretspec.toml, and they never leak silently into background agents the way a plain .env file can. That separation is why secrets belong in the secret store, not in devenv.nix.
Update the CLI with the upgrade command below, the same route you installed with. Update the project inputs with devenv update, which re-pins nixpkgs and the devenv modules in devenv.lock. Run both regularly, because a stale lockfile is how a teammate's fresh clone starts failing.
nix-env --upgrade --attr devenv -f https://github.com/NixOS/nixpkgs/tarball/nixpkgs-unstable
How Do I Create My First Devenv Environment?
Run devenv init inside your project directory, and it scaffolds devenv.yaml, devenv.nix, and .gitignore. The init command prints one line per file it creates, so you can see exactly what landed in your project. Commit devenv.yaml, devenv.nix, and devenv.lock, and leave the generated .devenv/ directory out of version control.
$ devenv init • Creating devenv.nix • Creating devenv.yaml • Creating .gitignore
The three files split the job cleanly: devenv.yaml holds inputs, devenv.nix holds the environment definition, and devenv.lock pins the input versions. That split is what makes the environment reproducible, because the lockfile freezes the exact nixpkgs revision everyone builds from. The Git beginner guide covers the version-control habits around these files, including why lockfiles belong in the repo.
| Block | What it declares | Example |
|---|---|---|
| env | Environment variables | env.GREET = "devenv" |
| packages | Extra tools on PATH | packages = [ pkgs.git ] |
| languages | Runtimes and language servers | languages.python.enable = true |
| services | Databases and servers | services.postgres.enable = true |
| processes | Long-running commands | processes.web.exec = "npm run dev" |
| tasks | Named runnable jobs | tasks."myapp:lint".exec = "golangci-lint run ./..." |
| scripts | Helper commands on PATH | scripts.test.exec = "pytest" |
| enterShell | Runs when the shell starts | enterShell = "echo ready" |
Open devenv.nix and you will see a Nix function that takes pkgs, lib, config, and inputs and returns an attribute set. The generated template is commented, so each block shows what it does before you edit it. Start by reading the whole file once, then change one block at a time and re-enter the shell to see the effect.
{ pkgs, lib, config, ... }:
{
env.GREET = "devenv";
packages = [ pkgs.git ];
languages.python.enable = true;
services.postgres.enable = true;
enterShell = ''
echo "Welcome to your devenv shell"
'';
}
How Do I Enter the Environment with devenv shell?
Run devenv shell and Nix builds your environment, then drops you into a shell where every declared tool is on PATH. The first run compiles the environment, and later runs are fast because devenv caches the evaluation. The vendor's own homepage claims a cached shell starts in under 100 ms, quoting 4,832 ms cold against 47 ms warm on the same configuration.
devenv shell
Anything in enterShell runs when the shell starts, which is where you put greetings, checks, or setup reminders. You can also run one command without entering the shell at all, with devenv shell followed by two dashes and the command. That form is handy in scripts and CI, where an interactive shell would only get in the way.
devenv shell -- python --version devenv shell -- npm install devenv shell -- cargo build
If the shell feels stale after you edit devenv.nix, save the file and press Ctrl+Alt+R to apply the rebuild without leaving the shell. That native reloading arrived in 2.0, and at launch it worked in bash, with zsh, fish, and nushell support landing in 2.1. The shell stays interactive the whole time, so you never lose your place.
Run devenv test before you push, because it builds the environment and runs every check in it. That is the same command CI runs, so a green test locally means the same green test in the pipeline. Make it a habit, and the lockfile stops drifting from what the team actually runs.
| Command | What it does |
|---|---|
| devenv init | Scaffolds devenv.yaml, devenv.nix, and .gitignore |
| devenv shell | Activates the developer environment |
| devenv up | Starts processes |
| devenv processes down | Stops background processes |
| devenv tasks run <task> | Runs a named task |
| devenv test | Builds the environment and runs checks |
| devenv container build|copy|run | Manages containers |
| devenv search <NAME> | Searches packages in the Nixpkgs input |
| devenv update | Updates and pins inputs into devenv.lock |
| devenv info | Prints environment information |
| devenv gc | Deletes unused environments |
| devenv mcp | Launches the MCP server for AI assistants |
How Do I Add a Language to Devenv?
Enable a language with one line in devenv.nix, for example languages.python.enable = true, and the runtime, its packages, and its language server arrive with the next shell. The homepage lists 58 languages, from Python and Go to Rust, Node, Ruby, PHP, Java, and Elixir. Pin a version when your project needs one, because the default tracks the rolling nixpkgs revision.
languages.python = {
enable = true;
version = "3.11";
};
A pinned Python version needs the nixpkgs-python input in devenv.yaml, because plain nixpkgs carries only one Python per revision. The virtual environment lives in the devenv state directory and activates automatically when you enter the shell. That replaces the old habit of creating a venv by hand and forgetting to activate it.
The same pattern works for every language: enable it, pin it if you must, and let the shell provide it. Node, Go, and Rust follow the identical shape, so learning one language block teaches you all of them. The full option reference at devenv.sh/reference/options/ lists every language and its options.
Each language can also enable its language server with lsp.enable, so your editor gets completions and diagnostics from the same environment. The server is configured per language, with lsp.package choosing which server binary to use. That keeps the editor config inside devenv.nix instead of scattered across editor settings.
How Do I Run Services Like Postgres Without Docker Compose?
Enable a service with one line, for example services.postgres.enable = true, and devenv starts a real PostgreSQL instance from your environment instead of a container. The homepage lists 43 preconfigured devenv services, including PostgreSQL, Redis, MySQL, RabbitMQ, MinIO, Caddy, and Elasticsearch. Services run as processes inside your project, so there is no compose file to maintain and no daemon to remember.
services.postgres = {
enable = true;
package = pkgs.postgresql_16;
port = 5432;
initialDatabases = [ { name = "myapp"; } ];
};
services.redis.enable = true;
Start the services with devenv up, and stop them with devenv processes down. The Docker beginner guide covers the container lane, and devenv services are the alternative for local development: the database is a process in your project, not a container on your machine. That trade is worth it when you want the database to start and stop with the project and nothing else.
Configure each service in the same block: pick the package version, set the port, and list the initial databases. The service stores its data inside the generated .devenv/ directory, so deleting that directory resets the database to a clean slate. That reset is a feature during development, and a warning before you store anything you care about in it.
Load local variables with dotenv.enable, which reads a .env file into the environment when the shell starts. Keep secrets out of that file, and keep the file itself out of version control. The same env block can also set variables directly, for values that are safe to share with the team.
How Do I Run Tasks with Devenv?
Declare a task in devenv.nix with tasks, then run it with devenv tasks run followed by the task name. Tasks are dependency-ordered and run in parallel where they can, which makes them a direct replacement for the Makefile glue most projects accumulate. The task name uses a namespace and a colon, for example myapp:lint, so tasks from different parts of the project never collide.
tasks."myapp:lint".exec = "golangci-lint run ./...";
tasks."myapp:test" = {
exec = "go test ./...";
after = [ "myapp:lint" ];
};
Lifecycle hooks let a task run at a shell event, for example running a setup task right after the shell starts. That hook is how you replace the README step that says run this script first, because the environment runs it for you. Keep tasks small and named after what they do, and the task list becomes the project's own documentation.
tasks."myproj:setup" = {
exec = "npm install";
};
"devenv:enterShell".after = [ "myproj:setup" ];
A task that fails stops the chain, so a failing lint task blocks the test task that depends on it. That ordering is the whole point: the environment knows the dependency graph, so you do not have to remember it.
How Does devenv up Work with the Native Process Manager?
Run devenv up and the native Rust process manager starts every declared process in dependency order, waiting for readiness probes before starting the next one. Dependencies use after with a process name, and a process can wait for another to be ready or completed. The old 1.x manager, process-compose, is not the default process manager in 2.x, and it is now opt-in through process.manager.implementation.
processes.web = {
exec = "npm run dev";
after = [ "devenv:processes:postgres" ];
};
Readiness probes come in three forms: exec, HTTP, and systemd notify, and the manager restarts a process that fails its probe. Port allocation is automatic, so two projects never fight over the same port unless you ask for strict ports. The HTTP probe is a small web request against your service, and if you need the background, what an API is explains the request-response shape behind it.
Stop everything with devenv processes down, or attach to a running process to watch its logs live. Attaching to running processes arrived in 2.2, and it means you can inspect a service without restarting it. That is the difference between a process manager and a script: the manager keeps state, restarts failures, and tells you why.
How Do I Use direnv with Devenv?
The devenv direnv integration enters your environment automatically when you cd into the project, using a two-line .envrc file. Since 2.0, direnv is optional, because devenv shell with native reloading is the primary workflow, and since 2.1 the devenv hook command replaces direnv for cd-based activation entirely. Use direnv when you want activation on cd, and use the hook when you want the same without installing direnv.
#!/usr/bin/env bash eval "$(devenv direnvrc)" use devenv
After writing .envrc, run direnv allow once to trust it, and the environment activates on every later cd into the directory. Do not copy the old pinned source_url form of the direnvrc from pre-2.0 guides, because that form is superseded and the current form is the eval line shown above. The 1.x-era guides still ranking for this topic teach direnv as near-mandatory, and that advice is outdated.
If you prefer no direnv at all, add one line to your shell configuration to enable devenv hook, and activation happens on cd without a .envrc file. Native shell integration loads automatically in fish and nushell since 2.2, while bash and zsh still need the one-line hook. Either way, the environment follows the directory, not the other way around.
How Do I Ship My Environment as a Container?
Build an OCI container from your environment with devenv container build followed by the container name. The predefined shell and processes containers cover the two common cases, and devenv container run starts the result with Docker. Copy it to a registry with devenv container copy, because the old devenv container --copy flag was removed in 2.0 and the subcommand is the only form now.
devenv container build processes devenv container run processes devenv container copy processes docker://ghcr.io/myorg/myapp
Containers need two extra inputs in devenv.yaml: nix2container and mk-shell-bin, added with devenv inputs add. Those inputs teach Nix how to assemble the container layers from your environment. The container carries the same packages and services the shell does, so what you tested locally is what ships.
devenv inputs add nix2container github:nlewo/nix2container devenv inputs add mk-shell-bin github:rrbutani/nix-mk-shell-bin
Start with the processes container when your app needs its database beside it, and the shell container when you only need the tools. The container is a deployment artifact, not a development replacement, and the shell stays the place where you write code. That split keeps the fast local loop and the reproducible artifact in the same project.
What Changed in Devenv 2.0 and After?
Version 2.0 rebuilt the interface: a terminal UI shows live progress for every command, a native Rust process manager replaced process-compose, and evaluation caching made repeat shells nearly instant. The v2.0 tag was published 2026-03-03, and the announcement blog followed on 2026-03-05, so guides that date 2.0 to March 5 are dating the announcement, not the release. Since 2.0, the project shipped 2.1 in May 2026 with native zsh, fish, and nushell support, 2.2 in July with devenv allow, and 2.4.0 in September 2026 with experimental Machines.
| Version | Published | Age on 2026-11-06 |
|---|---|---|
| v2.4.0 | 2026-09-24 | 43 days |
| v2.3.1 | 2026-09-11 | 56 days |
| v2.3 | 2026-09-07 | 60 days |
| v2.2 | 2026-07-28 | 101 days |
| v2.1 | 2026-05-05 | 185 days |
| v2.0 | 2026-03-03 | 248 days |
| v1.11.2 | 2025-11-27 | 344 days |
| v1.0 | 2024-03-20 | 961 days |
Three breaking changes catch upgraders: the git-hooks input is no longer included by default, so add it to devenv.yaml before git-hooks.hooks works; devenv build outputs JSON instead of plain store paths; and devenv 0.x is deprecated, with support ending in devenv 3. Learn those three before you upgrade, because each one looks like a bug the first time it bites.
| Area | 1.x | 2.x |
|---|---|---|
| Process manager | process-compose | Native Rust manager |
| direnv | Near-mandatory | Optional since 2.0 |
| git-hooks input | Included by default | Add to devenv.yaml |
| Container copy | container --copy flag | devenv container copy |
| Build output | Plain store paths | JSON |
| Shell reloading | None | Ctrl+Alt+R |
Across the 2.x line, 13 releases landed in 205 days, with gaps of 63, 84, 41, and 17 days between the minor releases. The repository is 1,476 days old with 7,807 stars and 586 forks, which works out to 13.32 stars per fork and a 7.5 percent fork ratio. The project was last pushed 2 days before this guide was written, so the 2.4.0 line is actively maintained.
from datetime import date
today = date(2026, 11, 6)
repo_created = date(2022, 10, 22)
v240 = date(2026, 9, 24)
v20 = date(2026, 3, 3)
print("repo age days:", (today - repo_created).days)
print("v2.4.0 age days:", (today - v240).days)
print("stars per fork:", round(7807 / 586, 2))
print("fork ratio percent:", round(586 / 7807 * 100, 1))
print("2.x span days:", (v240 - v20).days)
print("minor gaps days:", 63, 84, 41, 17)
print("warm speedup factor:", round(4832 / 47, 1))
# Output:
# repo age days: 1476
# v2.4.0 age days: 43
# stars per fork: 13.32
# fork ratio percent: 7.5
# 2.x span days: 205
# minor gaps days: 63 84 41 17
# warm speedup factor: 102.8
The guides still ranking for this topic predate all of it: the most complete independent walkthrough was published 2025-06-29, 495 days before this guide, and it teaches process-compose, mandatory direnv, and the old direnvrc form. A second personal guide was published 2023-08-26, 1,168 days ago, and updated 2026-08-05 without modernising to 2.x.
A third guide carries 2026 branding but its content is pre-2.0, and its publish date could not be verified. That staleness is the gap this guide fills, and it is why every command here is written against v2.4.0. CodexFuse offers no Devenv tool.
Devenv vs Devcontainers, Nix Flakes, Pixi, Devbox, and Flox
Every tool in this lane solves the same problem, reproducible environments, and the differences are about how much Nix you want to see. Devenv vs devcontainers is the closest call: devcontainers take away your own shell and tools, while devenv keeps your native shell and is naturally more reproducible, and devenv can even generate a devcontainer when you need one. Devenv vs nix develop is a question of abstraction: flakes are general-purpose and need deeper Nix knowledge, while devenv is the batteries-included interface for dev shells.
Devenv vs pixi is a different ecosystem question: pixi resolves conda-ecosystem project environments, while devenv is Nix dev shells with services, processes, and tasks, and no dedicated head-to-head comparison source exists for the two. Devbox's selling point is that you do not need to know Nix, while devenv is less abstract and more powerful but benefits from some Nix knowledge. Flox is another Nix-based environment tool with a manifest.toml file, and the only page-1 author who mentions it says he never used it, so no evaluated comparison exists there either.
| Tool | What it is | One-line difference from devenv |
|---|---|---|
| Devcontainers | VS Code containers for dev | Takes away your shell; devenv keeps it |
| Nix develop / flakes | General-purpose Nix shells | Needs deeper Nix knowledge |
| Pixi | Conda-ecosystem environments | Conda world, not Nix services |
| Devbox | Nix environments, simple JSON | No Nix knowledge needed, less power |
| Flox | Nix environments, manifest.toml | No evaluated comparison found |
Anyone searching for reproducible dev environments nix will meet the same five names, and the honest summary is that the lane has no single winner. Devenv wins when you want services, processes, and tasks in the same file as your languages, without giving up your shell. Start with devenv when that shape matches your project, and keep the comparison in mind when it does not.
Sources
Official sources fetched 2026-11-06: the devenv homepage at devenv.sh, the getting-started guide at devenv.sh/getting-started/, the basics guide at devenv.sh/basics/, the option reference at devenv.sh/reference/options/, the Python language guide at devenv.sh/languages/python/, the direnv integration page at devenv.sh/integrations/direnv/, and the containers guide at devenv.sh/containers/. Release history and repository facts come from the GitHub API for github.com/cachix/devenv and its releases endpoint, fetched the same day.
Version history comes from the official announcement posts: the 2.0 post at devenv.sh/blog/2026/03/05/devenv-20-a-fresh-interface-to-nix/, the 2.1 post at devenv.sh/blog/2026/05/07/devenv-21-nix-with-zsh-fish-and-nushell-via-libghostty/, the 2.2 post at devenv.sh/blog/2026/07/28/devenv-22-attach-to-running-processes-and-persistent-out-of-tree-environments/, and the 2.4 post at devenv.sh/blog/2026/09/24/devenv-24-machines/. The 2.3.1 fixes are noted in the 2.4 post, because no dedicated 2.3 release post was found.
Independent sources fetched 2026-11-06: the brunopaz.dev walkthrough published 2025-06-29, the haseebmajid.dev post published 2023-08-26 and updated 2026-08-05, the copyprogramming.com command-line guide, the eliteai.tools devenv skill reference, the zerotomastery.io setup guide, and the commandmasters.com command reference. Comparison details come from the haseebmajid.dev devcontainers aside, the eliteai.tools flakes trade-off note, the alan.norbauer.com Devbox introduction, the josephguhlin.com Pixi overview, and the alexhyett.com reproducible-environments newsletter.
Frequently Asked Questions
Is devenv free?
Yes, devenv costs nothing. It is open source under the Apache License 2.0, and there is no paid tier. The homepage lists 58 languages and 43 preconfigured services at no cost.
Do I need to learn Nix to use devenv?
Basic familiarity helps, but devenv.nix is a small declarative file, and most beginners copy the generated template and change one block at a time. Devbox is the alternative when you want reproducible environments without learning Nix at all.
Does devenv replace Docker?
No, devenv replaces docker-compose for local development services, not Docker itself. Your databases run as processes inside the project, and devenv container still builds real OCI images when you need to ship.
Does devenv work on Windows?
Devenv runs on Linux, macOS, and WSL2, and Windows users run it inside WSL2. The official getting-started page lists Windows under the WSL2 tab, so install WSL2 first and then follow the Linux steps inside it.
Should I use direnv with devenv?
Direnv is a choice, not a requirement, in 2.x, because devenv shell with native reloading is the primary workflow. If you want automatic activation on cd, use the current two-line .envrc form, or use devenv hook since 2.1 instead.




