Documentation Index
Fetch the complete documentation index at: https://mintlify.com/cachix/devenv/llms.txt
Use this file to discover all available pages before exploring further.
Profiles let you define named variants of your development environment inside a single devenv.nix file. A backend developer might only need a database and API server, while a frontend developer needs Node.js and a dev server. Profiles keep all of that in one place and let each person activate only the configuration they need.
Profiles were introduced in devenv v1.9.
Defining profiles
Define profiles in devenv.nix using the profiles option. Each profile has a .module attribute containing a standard devenv configuration:
{ pkgs, config, ... }: {
profiles = {
backend.module = {
services.postgres.enable = true;
services.redis.enable = true;
env.ENVIRONMENT = "backend";
};
frontend.module = {
languages.javascript.enable = true;
processes.dev-server.exec = "npm run dev";
env.ENVIRONMENT = "frontend";
};
testing.module = { pkgs, ... }: {
packages = [ pkgs.playwright pkgs.cypress ];
env.NODE_ENV = "test";
};
};
}
Activating profiles
Use the --profile flag with any devenv command to activate one or more profiles:
# Activate a single profile
$ devenv --profile backend shell
# Activate multiple profiles
$ devenv --profile backend --profile testing shell
When multiple profiles are active, devenv wraps every profile module in a deterministic priority order. Conflicting options are resolved by those priorities rather than by evaluation order, keeping the result predictable.
Referencing config within a profile
Each profile is a submodule that gets recursively merged into the top-level configuration. If you need to reference a value that is set within the same profile, make the profile module a function that receives its own config argument.
This example won’t work as expected — config here refers to the top-level configuration, which doesn’t yet include values set inside the profile:{ config, ... }:
{
profiles.dev.module = {
# References top-level config — postgres not yet known here
env.DB_HOST = config.env.PGHOST;
services.postgres.enable = true;
};
}
The solution is to make the profile module a function so it receives its own merged config:
{ config, ... }:
{
profiles.dev.module = { config, ... }: {
# Inner config includes both top-level and profile-specific values
env.DB_HOST = config.env.PGHOST;
services.postgres.enable = true;
};
}
Use the function form when your profile needs to reference config values that are set within the same profile. Profiles that only set static values, or only read from the top-level configuration, can use the plain attribute-set form.
Profile priorities
Profile priorities are assigned automatically so overrides are always predictable:
Base configuration
Always loads first and has the lowest precedence.
Hostname profiles
Activate next, based on the machine’s hostname.
User profiles
Activate after hostname profiles, based on the current username.
Manual profiles
Profiles passed with --profile have the highest precedence. If you pass several profiles, the last flag wins.
Extends chains resolve parents before children, so child profiles override their parents without extra lib.mkForce calls.
Here is a simple example where every tier toggles the same option, yet the final value stays deterministic:
{ config, ... }: {
myteam.services.database.enable = false;
profiles = {
hostname."dev-server".module = {
myteam.services.database.enable = true;
};
user."alice".module = {
myteam.services.database.enable = false;
};
qa.module = {
myteam.services.database.enable = true;
};
};
}
When Alice runs on dev-server, the hostname profile enables the database, her user profile disables it again, and devenv --profile qa shell flips it back on — all without any override helpers.
Extending profiles
Profiles can extend other profiles using the extends option, allowing hierarchical configurations that reduce duplication:
{
name = "myproject";
packages = [ pkgs.git pkgs.curl ];
languages.nix.enable = true;
profiles = {
backend = {
module = {
services.postgres.enable = true;
services.redis.enable = true;
};
};
frontend = {
module = {
languages.javascript.enable = true;
processes.dev-server.exec = "npm run dev";
};
};
fullstack = {
extends = [ "backend" "frontend" ];
};
};
}
Activating the fullstack profile automatically inherits everything from backend and frontend.
Automatic activation by hostname
Profiles under profiles.hostname activate automatically when the machine’s hostname matches:
{
profiles = {
work-tools.module = {
packages = [ pkgs.docker pkgs.kubectl pkgs.slack ];
};
hostname = {
"work-laptop" = {
extends = [ "work-tools" ];
module = {
env.WORK_ENV = "true";
services.postgres.enable = true;
};
};
"home-desktop".module = {
env.PERSONAL_DEV = "true";
};
};
};
}
Automatic activation by username
Profiles under profiles.user activate automatically when the current username matches:
{
profiles = {
developer-base.module = {
packages = [ pkgs.git pkgs.gh pkgs.jq ];
git.enable = true;
};
user = {
"alice" = {
extends = [ "developer-base" ];
module = {
env.USER_ROLE = "backend-developer";
languages.python.enable = true;
};
};
"bob" = {
extends = [ "developer-base" ];
module = {
env.USER_ROLE = "systems-engineer";
languages.go.enable = true;
languages.rust.enable = true;
};
};
};
};
}
Combining all profile types
All matching profiles are automatically merged when you run any devenv command. Given this configuration:
{
languages.nix.enable = true;
profiles = {
backend.module = {
services.postgres.enable = true;
};
hostname."ci-server".module = {
env.CI = "true";
packages = [ pkgs.buildkit ];
};
user."developer".module = {
git.enable = true;
packages = [ pkgs.gh ];
};
};
}
Running devenv --profile backend shell on a machine named ci-server with user developer activates all matching profiles at once:
- Base configuration (always active)
profiles.backend (via --profile)
profiles.hostname."ci-server" (automatic hostname match)
profiles.user."developer" (automatic username match)