Install
$ agentstack add skill-brpaz-agent-skills-devenv ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
devenv.sh - Nix-powered Developer Environments
Use this skill when modifying devenv.nix, devenv.yaml, or working with devenv CLI commands. This covers the full devenv configuration surface.
When to Use
- Creating or modifying a
devenv.nixordevenv.yamlfor a project - Adding packages, language runtimes, services, or scripts to a dev environment
- Integrating devenv with direnv for automatic environment activation
- Pinning or updating devenv inputs via
devenv.lock - Bootstrapping a web application environment around
Taskfile.yml,docker compose, and git hooks
Project File Structure
| 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) |
devenv.nix Structure
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 indevenv.yaml
devenv.yaml Structure
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
# Optional additional inputs
# inputs:
# nixpkgs-stable:
# url: github:NixOS/nixpkgs/nixos-24.11
# Optional imports from local files or inputs
# imports:
# - ./backend/devenv.nix
# - inputs:myinput/devenv-module.nix
# Allow unfree packages
# allowUnfree: true
# permittedUnfreePackages:
# - "terraform"
# Impure mode (access host env vars)
# impure: true
Environment Variables
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 path
Setting 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}";
}
Packages
{
# Add packages from nixpkgs
packages = [
pkgs.git
pkgs.jq
pkgs.curl
pkgs.go-task
pkgs.lefthook
pkgs.docker-compose
];
}
Search for packages: devenv search
Languages
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;
};
}
Scripts
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";
}
enterShell
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.
Web Application Default Stance
For web applications, prefer this split of responsibilities:
- devenv for toolchains, packages, env wiring, and shell bootstrapping
Taskfile.yml(go-task) for developer entrypoints liketask dev,task test, andtask lintdocker composefor long-running multi-service application stacks
For typical web applications, prefer Task + Compose over processes.*.
Recommended Defaults for Web Apps
- Put developer commands in
Taskfile.yml - Use
docker compose up/down/logsfor the running stack - Use
scripts.*indevenv.nixas thin wrappers aroundtask ...when convenient - Keep
processes.*for small local daemons, single binaries, or non-web workflows that do not need Compose
Example:
{
scripts.dev.exec = "task dev";
scripts.test.exec = "task test";
scripts.logs.exec = "task compose:logs";
}
Processes
Long-running processes managed by devenv (started with devenv up). These are useful for simple local daemons, workers, and lightweight tooling, but they are not the default orchestration choice for web applications:
{
# 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
};
}
Services
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/. Services start with devenv up.
Tasks
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
For web applications, prefer using project-level Taskfile.yml as the main task runner, and use devenv tasks only when you specifically want Nix-managed lifecycle hooks or shell-entry integration.
Tests
{
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";
};
}
Git Hooks (pre-commit)
Integration with git-hooks.nix:
{
git-hooks.hooks = {
nixfmt-rfc-style.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.
Overlays
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 {};
})
];
}
Inputs & Multiple Nixpkgs
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 (Building Artifacts)
{
outputs = {
my-app = config.languages.rust.import ./. {};
container = config.containers.shell.derivation;
};
}
Build: devenv build (all) or devenv build outputs.my-app
Profiles
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
{
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
Composing with Imports
# devenv.yaml
imports:
- ./backend/devenv.nix
- ./frontend/devenv.nix
- inputs:some-input/devenv-module.nix
For monorepos, use profiles or imports to compose per-service environments.
direnv Integration
.envrc file:
eval "$(devenv direnvrc)"
use devenv
This auto-activates the devenv shell when entering the project directory.
Dotenv Integration
{
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.
Creating Files
{
files."config/settings.json".text = builtins.toJSON {
port = 3000;
debug = true;
};
files.".editorconfig".text = ''
root = true
[*]
indent_style = space
indent_size = 2
'';
}
CLI Command Reference
| 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. | Build a specific output | | devenv search | Search available packages | | devenv info | Show environment info | | devenv update | Update and lock inputs | | devenv gc | Garbage collect old generations | | devenv container build | Build a container | | devenv container run | Run a container | | devenv container copy | Copy container to registry | | devenv tasks run | Run a specific task | | devenv processes up | Start processes (alias) |
Common flags:
--profile- Use a specific profile--impure- Allow impure evaluation (access host env)--option- Override a Nix option
Common Nix Patterns in devenv.nix
let/in bindings
{ pkgs, ... }:
let
port = "3000";
dbName = "myapp";
in {
env.PORT = port;
services.postgres.initialDatabases = [{ name = dbName; }];
}
Conditional configuration
{ 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 ];
}
Module imports
{ pkgs, ... }:
{
imports = [ ./shared.nix ];
# This merges with shared.nix configuration
packages = [ pkgs.curl ];
}
Rules
- ALWAYS read the existing
devenv.nixanddevenv.yamlbefore making changes - understand the current configuration. - NEVER manually edit
devenv.lock- it is auto-generated bydevenv update. - NEVER manually edit
.pre-commit-config.yaml- it is an auto-generated symlink from git-hooks configuration. - NEVER put real secrets in
devenv.nixor.envwith dotenv enabled - Nix store is world-readable. - Use
devenv searchto find the correct package attribute before adding topackages. - Prefer
Taskfile.yml(go-task) for web application task running - maketask dev,task test, andtask lintthe main developer entrypoints. - Prefer
docker composeoverprocesses.*for web application stacks - reserveprocesses.*for simpler local daemons and non-web workflows. - Prefer tasks over enterShell for setup steps that can be cached or have dependencies.
- Keep
enterShellsmall and idempotent - copying.env.exampleto.envis fine; installs, migrations, and long-running commands are not. - Use
lib.mkIffor conditional configuration, not string interpolation orif/then/elseat the module level. - Pin binaries in scripts with
${pkgs.foo}/bin/foowhen PATH conflicts are possible. - Use
devenv.local.nixfor personal overrides that should not be committed. - Run
devenv testto validate changes - it exercises the full environment including processes.
Starter Template
A basic starter is bundled at skills/devenv/templates/basic-webapp/.
It includes:
devenv.nixdevenv.yaml.envrc.env.examplecompose.yamlTaskfile.ymllefthook.yml
The template is intentionally minimal and shows the preferred pattern for web apps in this repository:
devenvfor packages and shell bootstrapTaskfile.ymlfor task runningdocker composefor the running stacklefthookfor git hook wiring
Examples
# devenv.nix — basic web app shell
{ pkgs, ... }: {
packages = [
pkgs.git
pkgs.go-task
pkgs.lefthook
pkgs.docker-compose
];
enterShell = ''
if [ ! -f .env ] && [ -f .env.example ]; then
cp .env.example .env
fi
'';
scripts.dev.exec = "task dev";
scripts.test.exec = "task test";
}
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: brpaz
- Source: brpaz/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.