一键导入
devenv
Define Nix-based development environments with devenv.sh, including task runners, services, and containers.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Define Nix-based development environments with devenv.sh, including task runners, services, and containers.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Complete API for Google NotebookLM - full programmatic access including features not in the web UI. Create notebooks, add sources, generate all artifact types, download in multiple formats. Activates on explicit /notebooklm or intent like "create a podcast about X"
Use this skill only when the user specifically requires a language assistant fluent in Czech (čeština).
Use this skill when the users asks for help creating or improving a skill, or when they ask for best practices in skill creation. This skill provides guidance on how to write well-scoped, effective skills that trigger reliably on relevant prompts.
| name | devenv |
| description | Define Nix-based development environments with devenv.sh, including task runners, services, and containers. |
| tags | ["nix","devenv","development-environment","nixos","direnv"] |
Use this skill when modifying devenv.nix, devenv.yaml, or working with devenv CLI commands. This covers the full devenv configuration surface.
devenv.nix or devenv.yaml for a projectdevenv.lockTaskfile.yml, docker compose, and git hooks| File | Purpose | Commit? |
|---|---|---|
devenv.nix | Main configuration (Nix expression) | Yes |
devenv.yaml | Inputs, imports, and settings | Yes |
devenv.lock | Pinned input revisions (auto-generated) | Yes |
.envrc | direnv integration (use devenv) | Yes |
devenv.local.nix | Local overrides (not committed) | No |
devenv.local.yaml | Local input overrides | No |
.devenv/ | Generated state directory | No (gitignored) |
.devenv.flake.nix | Auto-generated flake wrapper | No (gitignored) |
Every devenv.nix is a Nix function receiving a module argument set:
{ pkgs, lib, config, inputs, ... }:
{
# Configuration goes here
}
Key parameters:
pkgs - Nixpkgs package setlib - Nixpkgs library functionsconfig - The resolved devenv configuration (self-referencing)inputs - Flake inputs defined in devenv.yamlinputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
# Optional additional inputs
# inputs:
# nixpkgs-stable:
# url: github:NixOS/nixpkgs/nixos-24.11
Built-in variables available inside devenv shell:
DEVENV_ROOT - Project root directoryDEVENV_DOTFILE - Path to .devenv/ directoryDEVENV_STATE - Persistent state directory (for services data)DEVENV_RUNTIME - Runtime directory (temp, cleared on reboot)DEVENV_PROFILE - Current Nix profile pathSetting custom env vars:
{
env.DATABASE_URL = "postgres://localhost:5432/mydb";
env.API_PORT = "3000";
# Reference other config values
env.GREETING = "Running in ${config.env.DEVENV_ROOT}";
}
{
# Add packages from nixpkgs
packages = [
pkgs.git
pkgs.jq
pkgs.curl
pkgs.go-task
pkgs.lefthook
pkgs.docker-compose
];
}
Search for packages: devenv search <name>
Languages are enabled via toggle modules. Each language module provides tooling, compilers, and package managers.
{
# Node.js
languages.javascript = {
enable = true;
package = pkgs.nodejs_22; # Override default version
pnpm.enable = true;
pnpm.install.enable = true; # Auto-install on shell entry
};
# Python
languages.python = {
enable = true;
version = "3.12";
venv.enable = true;
venv.requirements = ./requirements.txt;
};
# Rust
languages.rust = {
enable = true;
channel = "stable"; # "stable", "nightly", or "beta"
};
# Go
languages.go.enable = true;
# Ruby
languages.ruby = {
enable = true;
bundler.enable = true;
};
}
Custom commands available in the devenv shell:
{
# Simple script
scripts.dev.exec = ''
pnpm run dev
'';
# Script with extra packages in scope
scripts.fetch-data.exec = ''
curl -s https://api.example.com | jq '.data'
'';
scripts.fetch-data.packages = [ pkgs.curl pkgs.jq ];
# Pin binaries to avoid PATH conflicts
scripts.build.exec = ''
${pkgs.nodejs}/bin/node scripts/build.js
'';
# Use a specific interpreter
scripts.analyze.exec = ''
import sys
print(sys.version)
'';
scripts.analyze.package = config.languages.python.package;
scripts.analyze.binary = "python3";
}
Code that runs every time you enter the devenv shell:
{
enterShell = ''
if [ ! -f .env ] && [ -f .env.example ]; then
cp .env.example .env
echo "Created .env from .env.example"
fi
'';
}
Use enterShell only for small, idempotent shell-entry bootstrap such as creating a missing local .env file from .env.example.
Prefer tasks over enterShell for anything non-trivial - tasks support better reuse, clearer entrypoints, and easier composition with project tooling.
For web applications, prefer this split of responsibilities:
Taskfile.yml (go-task) for developer entrypoints like task dev, task test, and task lintdocker compose for long-running multi-service application stacksFor typical web applications, prefer Task + Compose over processes.*.
Taskfile.ymldocker compose up / down / logs for the running stackscripts.* in devenv.nix as thin wrappers around task ... when convenientprocesses.* for small local daemons, single binaries, or non-web workflows that do not need ComposeExample:
{
scripts.dev.exec = "task dev";
scripts.test.exec = "task test";
scripts.logs.exec = "task compose:logs";
}
Long-running processes managed by devenv (started with devenv up). These are useful for simple local daemons, workers, and lightweight tooling, socket activation, file watching, and dependency management.
{
# Simple process
processes.api.exec = "pnpm run dev";
# Process with working directory
processes.frontend = {
exec = "pnpm run dev";
cwd = "./frontend";
};
# Process dependencies (start order)
processes.api = {
exec = "pnpm run start:api";
after = [ "devenv:processes:db" ];
};
# File watching (auto-restart on changes)
processes.worker = {
exec = "node worker.js";
watch.paths = [ ./src/worker ];
};
# Port allocation
processes.api = {
exec = "node server.js --port $PORT";
ports.http.allocate = 8080;
# Access: config.processes.api.ports.http.value
};
}
Process management is handled via the devenv processes command suite or the devenv MCP server.
[!IMPORTANT] Use the MCP Server to Manage Processes when Available: If the
devenvMCP server is enabled in your environment, always prefer using the MCP tools (list_processes,start_process,stop_process,get_process_status,get_process_logs) over shell CLI commands. The MCP server is executed in the exact context of the workspace's background daemon, which prevents the path and socket mismatches described below.
CLI Commands are Not Shareable Across Shells:
devenv process manager states and sockets are saved in a temporary runtime directory (DEVENV_RUNTIME) keyed to a hash of the environment. If you enter the environment in different ways (e.g. nested shells, different terminal sessions, or wrapper scripts), their DEVENV_RUNTIME values may point to different directories (e.g., /tmp/devenv-e864f60 vs /tmp/devenv-c92e103).
As a result, commands like devenv processes list or devenv processes down run in one session might fail to find the active supervisor's control socket and report "No process manager is running", even though the supervisor is running and actively managing processes in the background.
Do Not Run Processes in Ephemeral or Nested Shells:
Avoid running devenv up or devenv up -d inside ephemeral shells (such as nested scripts, short-lived tasks, or subagent runs). If the shell parent exits or is cancelled, the supervisord daemon may exit or lose its control socket, but the children (Vite, Go backend, Postgres) will remain orphaned, running invisibly in the background, consuming CPU, and blocking port bindings.
Prefer Foreground in Persistent/Backgrounded Shell Sessions:
To ensure processes clean up automatically, run devenv up or devenv processes start in the foreground of a terminal session. If you need the stack to run in the background, background the terminal session itself (e.g., using tmux, screen, or a background system daemon), while keeping the processes running in the foreground of that session.
This way, when the session is closed or killed, the terminal controller sends signals (SIGINT/SIGHUP) to the entire process group, ensuring all child services are cleanly terminated instead of being orphaned.
Testing Foreground Exit Behavior:
You can test this clean teardown using terminal multiplexers (like tmux):
devenv up in a detached tmux session:
tmux new-session -d -s devenv-session 'devenv up'
lsof -i :3000).tmux kill-session -t devenv-session
Shutting Down Orphaned Supervisors:
If a supervisor daemon gets orphaned or runs under a mismatched socket path, manual child process kills (e.g. killing postgres or node) will just trigger immediate restarts by the supervisor. To shut down the daemon and its children permanently:
pkill -f "daemon-processes"
pkill -f "node.*vite" || true
pkill -f "postgres" || true
Start all processes in the background:
devenv processes start
(Alternatively, you can run devenv up -d to start the process manager in detached mode).
Start a specific process:
devenv processes start <process-name>
Example:
devenv processes start service
List all processes and their current status/phase:
devenv processes list
Check details of a specific process:
devenv processes status <process-name>
Example:
devenv processes status service
devenv processes logs <process-name>
Example:
devenv processes logs service
Stop a specific process:
devenv processes stop <process-name>
Example:
devenv processes stop service
Stop the process manager and shut down all processes:
devenv processes down
Pre-built service modules (databases, caches, etc.):
{
services.postgres = {
enable = true;
listen_addresses = "127.0.0.1";
port = 5432;
initialDatabases = [{ name = "myapp"; }];
extensions = ext: [ ext.postgis ];
};
services.redis.enable = true;
services.mysql = {
enable = true;
initialDatabases = [{ name = "myapp"; }];
};
services.minio.enable = true;
services.elasticsearch.enable = true;
}
Service state is stored in $DEVENV_STATE/<service>. Services start with devenv up.
Cacheable, dependency-aware build tasks inside devenv.nix:
{
# Basic task
tasks."myapp:build" = {
exec = "pnpm run build";
before = [ "devenv:enterShell" ]; # Runs before shell entry
};
# Task with status check (skip if already done)
tasks."myapp:install" = {
exec = "pnpm install";
status = "test -d node_modules";
before = [ "devenv:enterShell" ];
};
# Conditional re-execution on file changes
tasks."myapp:codegen" = {
exec = "pnpm run codegen";
execIfModified = [ "schema/**/*.graphql" ];
before = [ "devenv:enterShell" ];
};
# Task with inputs (CLI-passable)
tasks."myapp:migrate" = {
exec = ''
echo "Running migration: $TASK_INPUT_NAME"
'';
input = {
name = {
description = "Migration name";
default = "latest";
};
};
};
}
Run tasks: devenv tasks run myapp:build
With inputs: devenv tasks run myapp:migrate --input name=v2
TIP: You can also use the devenv MCP server to manage processes, tasks, read their logs, etc.
{
enterTest = ''
echo "Running tests..."
wait_for_port 5432 # Helper: wait for service port
pnpm run test
'';
}
Run: devenv test (starts processes, runs enterTest, then stops everything).
Use config.devenv.isTesting to conditionally disable things during tests:
{
processes.heavy-worker = lib.mkIf (!config.devenv.isTesting) {
exec = "node heavy-worker.js";
};
}
Integration with git-hooks.nix:
{
git-hooks.hooks = {
nixfmt.enable = true;
prettier = {
enable = true;
excludes = [ "pnpm-lock.yaml" ];
};
eslint.enable = true;
shellcheck.enable = true;
# Custom hook
check-todos = {
enable = true;
entry = "grep -r 'ACTION_ITEM' --include='*.ts' && exit 1 || exit 0";
language = "system";
};
};
}
The .pre-commit-config.yaml is an auto-generated symlink - do not edit it directly.
Modify or extend the nixpkgs package set:
{
overlays = [
# Override an existing package
(final: prev: {
hello = prev.hello.overrideAttrs (old: {
patches = (old.patches or []) ++ [ ./my-patch.patch ];
});
})
# Add a custom package
(final: prev: {
my-tool = final.callPackage ./nix/my-tool.nix {};
})
];
}
In devenv.yaml, add extra inputs:
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
nixpkgs-stable:
url: github:NixOS/nixpkgs/nixos-24.11
Use in devenv.nix:
{ pkgs, inputs, ... }:
let
pkgs-stable = import inputs.nixpkgs-stable {
system = pkgs.stdenv.system;
};
in {
packages = [
pkgs.nodejs_22 # From rolling
pkgs-stable.terraform # From stable
];
}
Update/lock inputs: devenv update
{
outputs = {
my-app = config.languages.rust.import ./. {};
container = config.containers.shell.derivation;
};
}
Build: devenv build (all) or devenv build outputs.my-app
Named configurations for different contexts:
# devenv.nix
{
# Shared config available to all profiles
packages = [ pkgs.git ];
}
# profiles/backend.nix - referenced in devenv.yaml imports
{ pkgs, ... }: {
languages.go.enable = true;
services.postgres.enable = true;
}
Activate: devenv --profile backend shell
{
containers.shell.name = "my-app-shell";
containers.processes = {
name = "my-app";
startupCommand = config.procfileScript;
};
}
Build: devenv container build shell
Run: devenv container run shell
Copy to registry: devenv container copy shell docker://registry/image:tag
For monorepos, use profiles or imports to compose per-service environments.
{
dotenv.enable = true;
dotenv.filename = ".env"; # default
}
Warning: .env contents are copied into the Nix store (world-readable). For real secrets, use secretspec or inject via environment outside of Nix.
{
files."config/settings.json".text = builtins.toJSON {
port = 3000;
debug = true;
};
files.".editorconfig".text = ''
root = true
[*]
indent_style = space
indent_size = 2
'';
}
| Command | Description |
|---|---|
devenv init | Initialize a new devenv project |
devenv shell | Enter the development shell |
devenv up | Start all processes |
devenv up -d | Start processes in background (detached) |
devenv test | Run tests (starts processes, runs enterTest, stops) |
devenv build | Build all outputs |
devenv build outputs.<name> | Build a specific output |
devenv search <query> | Search available packages |
devenv info | Show environment info |
devenv update | Update and lock inputs |
devenv gc | Garbage collect old generations |
devenv container build <name> | Build a container |
devenv container run <name> | Run a container |
devenv container copy <name> <url> | Copy container to registry |
devenv tasks run <name> | Run a specific task |
devenv processes up | Start processes (alias) |
Common flags:
--profile <name> - Use a specific profile--impure - Allow impure evaluation (access host env)--option <key> <value> - Override a Nix option{ pkgs, ... }:
let
port = "3000";
dbName = "myapp";
in {
env.PORT = port;
services.postgres.initialDatabases = [{ name = dbName; }];
}
{ pkgs, lib, config, ... }:
{
# Only include when testing
processes.seed-db = lib.mkIf config.devenv.isTesting {
exec = "node seed.js";
};
# Optional attrs
packages = [ pkgs.git ] ++
lib.optionals pkgs.stdenv.isLinux [ pkgs.strace ];
}
{ pkgs, ... }:
{
imports = [ ./shared.nix ];
# This merges with shared.nix configuration
packages = [ pkgs.curl ];
}
devenv.nix and devenv.yaml before making changes - understand the current configuration.devenv.lock - it is auto-generated by devenv update..pre-commit-config.yaml - it is an auto-generated symlink from git-hooks configuration.devenv.nix or .env with dotenv enabled - Nix store is world-readable.devenv search <name> to find the correct package attribute before adding to packages.enterShell small and idempotent - copying .env.example to .env is fine; installs, migrations, and long-running commands are not.lib.mkIf for conditional configuration, not string interpolation or if/then/else at the module level.${pkgs.foo}/bin/foo when PATH conflicts are possible.devenv.local.nix for personal overrides that should not be committed.devenv test to validate changes - it exercises the full environment including processes.