devenv
Skill1 starsChanged 6 months agoMITView on GitHub
Define Nix-based development environments with devenv.sh, including task runners, Docker Compose workflows, services, and containers.
- Reads credentials
We split this skill into the rules it gives an agent
11 rules. 0 are common, 0 are distinctive, and 11 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
0
Distinctive
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 10
Use this skill when modifying devenv.nix, devenv.yaml, or working with devenv CLI commands.
- Very specific: line 35
Every devenv.nix is a Nix function receiving a module argument set:
- Very specific: line 114
Search for packages: devenv search <name>
- Very specific: line 202
Use enterShell only for small, idempotent shell-entry bootstrap such as creating a missing local .env file from .env.example.
- Very specific: line 218
Put developer commands in Taskfile.yml
- Very specific: line 219
Use docker compose up / down / logs for the running stack
- Very specific: line 220
Use scripts.* in devenv.nix as thin wrappers around task ...
- Very specific: line 221
Keep processes.* for small local daemons, single binaries, or non-web workflows that do not need Compose
- Very specific: line 356
Run: devenv test (starts processes, runs enterTest, then stops everything).
- Very specific: line 358
Use config.devenv.isTesting to conditionally disable things during tests:
- Very specific: line 446
Update/lock inputs: devenv update
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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: trueEnvironment 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 <name>
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/<service>. 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.11Use 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.nixFor monorepos, use profiles or imports to compose per-service environments.
direnv Integration
.envrc file:
eval "$(devenv direnvrc)"
use devenvThis 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.<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
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 search <name>to 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";
}Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- flexprice/flexprice★ 6.9ksame name
- kissgyorgy/coding-agents★ 15same name
- jskswamy/skills★ 3same name
- wyattjoh/skills★ 2same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository36 other files it gives its agents.
- skills/backend-best-practices/AGENTS.mdAGENTS.md
- adr-writerSkill
- application-healthchecksSkill
- backend-best-practicesSkill
- bspec-prd-discoverySkill
- bspec-story-implementationSkill
- bspec-story-reviewSkill
- bspec-story-syncSkill
- bspec-technical-architectureSkill
- bspec-workflowSkill
- conventional-commitsSkill
- copier-scaffoldSkill
- direnvSkill
- docker-composeSkill
- dockerfileSkill
- docker-setupSkill
- docs-writerSkill
- github-actions-dockerSkill
- gitleaks-setupSkill
- golang-developmentSkill
- golang-slogSkill
- golang-testingSkill
- playwright-devenvSkill
- project-webappSkill
- readme-writerSkill
- release-drafter-setupSkill
- release-drafterSkill
- release-notesSkill
- renovate-pr-triageSkill
- renovate-setupSkill
- structured-loggingSkill
- summarize-articleSkill
- summarize-youtube-videoSkill
- tailwind-v4Skill
- vicinae-extensionsSkill
- zensical-setupSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

