Skip to content

Processes

Devenv provides built-in process management with supervision, socket activation, file watching, and dependency management.

Basic Example

devenv.nix
{ pkgs, ... }:

{
  processes = {
    silly-example.exec = "while true; do echo hello && sleep 1; done";
    ping.exec = "ping localhost";
    server = {
      exec = "python -m http.server";
      cwd = "./public";
    };
  };
}

To start the processes, run:

$ devenv up

Dependencies

Processes can depend on other processes using after and before:

devenv.nix
{
  processes = {
    database.exec = "postgres";

    api = {
      exec = "myapi";
      after = [ "devenv:processes:database" ];  # wait for database to be ready
    };
  };
}

Use @complete to wait for a process to stop (soft dependency), or @ready (default) for readiness.

Using Pre-built Services

Devenv provides many pre-configured services with proper process management. See the Services documentation for available services like:

These services come with sensible defaults, health checks, and proper initialization scripts.

Restart Policies

New in devenv 2.0

Control how processes restart when they exit:

  • on_failure (default) - restart only on non-zero exit
  • always - restart on any exit
  • never - never restart
devenv.nix
{
  processes.worker = {
    exec = "worker --queue jobs";
    restart = {
      on = "always";
      max = 10;  # null for unlimited (default: 5)
    };
  };
}

Ready Probes

New in devenv 2.0

Ready probes let the process manager detect when a process is ready to serve. This is used by after dependencies to know when a dependency is available.

Exec probe

Run a shell command to check readiness. Exit code 0 means ready:

devenv.nix
{
  processes.database = {
    exec = "postgres -D $PGDATA";
    ready = {
      exec = "pg_isready -d template1";
    };
  };
}

HTTP probe

Poll an HTTP endpoint for readiness:

devenv.nix
{
  processes.api = {
    exec = "myserver";
    ready = {
      http.get = {
        port = 8080;
        path = "/health";
        # host = "127.0.0.1";  # default
        # scheme = "http";     # default
      };
    };
  };
}

Notify probe

Use systemd-style readiness notification. Your process should send READY=1 to the socket path in $NOTIFY_SOCKET:

devenv.nix
{
  processes.database = {
    exec = "postgres";
    ready.notify = true;
  };

  processes.api = {
    exec = "myapi";
    after = [ "devenv:processes:database" ];  # waits for READY=1
  };
}

Probe timing options

All probe types support these timing options:

devenv.nix
{
  processes.api = {
    exec = "myserver";
    ready = {
      http.get = { port = 8080; path = "/health"; };
      initial_delay = 2;    # seconds before first probe (default: 0)
      period = 10;           # seconds between probes (default: 10)
      timeout = 1;           # seconds before probe times out (default: 1)
      success_threshold = 1; # consecutive successes needed (default: 1)
      failure_threshold = 3; # consecutive failures before unhealthy (default: 3)
    };
  };
}

When listen sockets or allocated ports are configured and no explicit probe is set, a TCP connectivity check is used automatically.

File Watching

New in devenv 2.0

Automatically restart processes when files change:

devenv.nix
{
  processes.backend = {
    exec = "cargo run";
    watch = {
      paths = [ ./src ];
      extensions = [ "rs" "toml" ];
      ignore = [ "target" "*.log" ];
    };
  };
}

Socket Activation

New in devenv 2.0

Socket activation allows the process manager to bind sockets before starting your process. This enables zero-downtime restarts and lazy process startup.

devenv.nix
{
  processes.api = {
    exec = "myserver";
    listen = [
      {
        name = "http";
        kind = "tcp";
        address = "127.0.0.1:8080";
      }
      {
        name = "admin";
        kind = "unix_stream";
        path = "$DEVENV_STATE/admin.sock";
      }
    ];
  };
}

Your process receives these environment variables:

  • LISTEN_FDS - number of passed file descriptors
  • LISTEN_PID - PID that should accept the sockets
  • LISTEN_FDNAMES - colon-separated socket names

File descriptors start at 3 (after stdin, stdout, stderr). This is compatible with systemd socket activation.

Watchdog

New in devenv 2.0

Enable systemd-compatible watchdog monitoring. Your process must periodically send WATCHDOG=1 to the notify socket, or it will be killed and restarted:

devenv.nix
{
  processes.api = {
    exec = "myserver";
    ready.notify = true;
    watchdog = {
      usec = 30000000;      # 30 seconds
      require_ready = true;  # only enforce after READY=1 (default)
    };
  };
}

Git Integration

Processes can reference the git repository root path using ${config.git.root}, useful in monorepo environments:

devenv.nix
{ config, ... }:

{
  processes.frontend = {
    exec = "npm run dev";
    cwd = "${config.git.root}/frontend";
  };

  processes.backend = {
    exec = "cargo run";
    cwd = "${config.git.root}/backend";
  };
}

Processes are automatically available as tasks, allowing you to define pre and post hooks. See the Processes as tasks section for details.

Automatic port allocation

New in devenv 2.0

Devenv can automatically allocate free ports for your processes, preventing conflicts when a port is already in use or when running multiple devenv projects simultaneously.

Define ports using ports.<name>.allocate with a base port number. Devenv will find a free port starting from that base, incrementing until one is available:

devenv.nix
{ config, ... }:

{
  processes.server = {
    ports.http.allocate = 8080;
    ports.admin.allocate = 9000;
    exec = ''
      echo "HTTP server on port ${toString config.processes.server.ports.http.value}"
      echo "Admin panel on port ${toString config.processes.server.ports.admin.value}"
      python -m http.server ${toString config.processes.server.ports.http.value}
    '';
  };
}

The resolved port is available via config.processes.<name>.ports.<port>.value. If port 8080 is already in use, devenv will automatically try 8081, 8082, and so on until it finds an available port.

Devenv holds the allocated ports during configuration evaluation to prevent race conditions, then releases them just before starting the processes so your application can bind to them.

This is particularly useful for:

  • Running multiple projects: Each project gets its own ports without manual coordination
  • CI environments: Tests can run in parallel without port conflicts
  • Shared development machines: Multiple developers can run the same project simultaneously

Strict port mode

If you want devenv to fail when a port is already in use instead of automatically finding the next available port, use the --strict-ports flag:

$ devenv up --strict-ports

This is useful when you need deterministic port assignments and want to be notified of conflicts rather than having them silently resolved. When a port conflict is detected in strict mode, devenv will show an error message including which process is currently using the port.

Alternative Process Managers

By default, devenv uses its native process manager. You can switch to alternative implementations:

To switch:

devenv.nix
{
  process.manager.implementation = "process-compose";
}