Skip to content

Profiles

Profiles allow you to organize different variations of your development environment. You can activate profiles manually using CLI flags or have them activate automatically based on your system environment.

Basics

Define profiles in your devenv.nix file using the profiles option:

{ 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";
    };
  };
}

Use the --profile flag 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. Conflicting options are resolved by those priorities instead of relying on evaluation order.

Profile priorities

Profile priorities are assigned automatically so you can reason about overrides:

  • Base configuration always loads first and has the lowest precedence.
  • Hostname profiles activate next, followed by user profiles.
  • Manual 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 mkForce calls.

This ordering keeps large profile stacks predictable even when several profiles change the same option.

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 a manual devenv --profile qa shell flips it back on. Conflicts resolve in priority order without any extra override helpers.

Merging profiles

Profiles can extend other profiles using the extends option, allowing you to build hierarchical configurations and 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" ];
    };
  };
}

Hostname Profiles

Profiles can automatically activate based on your machine's hostname:

{
  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";
      };
    };
  };
}

User Profiles

Profiles can automatically activate based on your username:

{
  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;
        };
      };
    };
  };
}

Composition

All matching profiles are automatically merged when you run devenv commands:

{
  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 ];
    };
  };
}

When you run devenv --profile backend shell on a machine named "ci-server" with user "developer", all matching profiles activate:

  • Base configuration (always active)
  • profiles.backend (via --profile)
  • profiles.hostname."ci-server" (automatic hostname match)
  • profiles.user."developer" (automatic user match)