Processes
Devenv provides built-in process management with supervision, socket activation, file watching, and dependency management.
Basic Example
{ 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:
To stop detached processes:
New in devenv 2.1.3
devenv down is a shorthand for devenv processes down.
To wait for all processes to become ready (useful in CI):
The default timeout is 120 seconds.
Controlling when processes start
New in devenv 2.1.3
Each process has a start.enable option that controls when it starts. It accepts:
true(default) — start withdevenv up.false— never auto-start. The process is still registered and visible in the TUI as stopped; start it manually (select it and press Enter, ordevenv processes start <name>)."interactive-shell"— start when you enter an interactivedevenv shelland stop it when you exit the shell. It is also started bydevenv up.
Set the default for every process with process.start, and override per process with processes.<name>.start.enable:
{
# Default: tie every process to the shell lifecycle.
process.start = "interactive-shell";
processes.web.exec = "npm run dev";
# Override a single process to only start with `devenv up`.
processes.worker = {
exec = "worker --queue jobs";
start.enable = true;
};
}
With start.enable = "interactive-shell", a single devenv shell starts your processes in the background, drops you into a shell with your tools, and stops the processes when you exit — replacing the devenv up -d && devenv shell two-step (and the matching exit + devenv processes down).
Only an interactive devenv shell starts these processes: devenv shell -- <cmd>, a piped/non-interactive shell, and devenv shell --no-reload do not (devenv warns when it skips them). Use devenv up to start them outside an interactive shell.
The "interactive-shell" start mode requires the native process manager (the default for devenv 2.0+).
Attaching with devenv up
If a process manager is already running (for example started by devenv shell or devenv up -d), running devenv up attaches to it over its control socket and starts the remaining up-enabled processes, instead of failing with "Processes already running".
Pressing Ctrl-C detaches the foreground view. The processes keep running for as long as the manager does:
- If the manager was started by
devenv up -d, it persists until you rundevenv down. - If the manager was started by
devenv shell, the manager and every process inside it (including ones an attacheddevenv upstarted) stop when you exit that shell.
Attaching is supported by the native process manager.
Dependencies
Processes can depend on other processes and tasks using after and before:
{
processes = {
database.exec = "postgres";
api = {
exec = "myapi";
after = [ "devenv:processes:database" ]; # wait for database to be ready
};
};
}
Dependency suffixes control when a dependency is considered satisfied.
For process dependencies:
@started— wait for the process to begin execution@ready(default) — wait for the readiness probe to pass@completed— wait for the process to finish, regardless of exit code (soft dependency, does not propagate failure)
For task dependencies:
@started— wait for the task to begin execution@succeeded(default) — wait for the task to exit with code 0@completed— wait for the task to finish, regardless of exit code (soft dependency, does not propagate failure)
See Dependency states for the full semantics, and Execution modes for how devenv up and devenv tasks run decide which dependencies to schedule.
Setup tasks that run after a process
devenv up schedules processes in before mode, which runs each process's upstream dependencies but not tasks that run after it. A setup or configure task wired downstream of a process — e.g. processes.<name>.before = [ "devenv:<name>:configure" ] — is skipped under devenv up and never runs. Use devenv up --mode all, or see Processes as tasks for details.
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 exitalways- restart on any exitnever- never restart
{
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:
{
processes.database = {
exec = "postgres -D $PGDATA";
ready = {
exec = "pg_isready -d template1";
};
};
}
HTTP probe
Poll an HTTP endpoint for readiness:
{
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:
{
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:
{
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)
probe_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)
# timeout = ; Overall deadline in seconds for the process to become ready. null = no deadline.
};
};
}
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:
{
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.
{
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 descriptorsLISTEN_PID- PID that should accept the socketsLISTEN_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:
{
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:
{ 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:
{ 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, you can set the default in devenv.yaml:
Or override it for a single run with CLI flags:
The CLI flags take precedence over the config value.
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:
- process-compose - Feature-rich external process manager with TUI
- overmind - Procfile-based with tmux integration
- honcho - Python Foreman port
- hivemind - Simple Procfile manager
- mprocs - TUI process manager
To switch: