Migrating to devenv 2.0
This guide covers the breaking changes in devenv 2.0 and how to update your project.
Native process manager is the default
Section titled “Native process manager is the default”devenv 2.0 replaces process-compose with a built-in Rust process manager. If your processes work without process-compose-specific configuration, no changes are needed — the native manager picks up processes.* definitions as before.
If you depend on process-compose features or want to keep using it during the transition:
{ process.manager.implementation = "process-compose";}The native manager supports port allocation, readiness probes, socket activation, file watching, dependency ordering, watchdog heartbeats, and Linux capabilities. See the processes documentation for details.
If there’s something process-compose does that the native manager doesn’t yet cover, please let us know.
Migrating process-compose options
Section titled “Migrating process-compose options”If you used processes.<name>.process-compose attributes, here’s how to translate them to native equivalents.
Dependencies
Section titled “Dependencies”process-compose uses depends_on with conditions. The native manager uses after with lifecycle suffixes:
{ processes.api.process-compose = { depends_on.postgres.condition = "process_healthy"; depends_on.migrations.condition = "process_completed_successfully"; depends_on.cleanup.condition = "process_completed"; };}{ processes.api.after = [ "devenv:processes:postgres" # waits for readiness probe (= process_healthy) "devenv:processes:migrations@succeeded" # waits for successful completion "devenv:processes:cleanup@completed" # waits for exit regardless of success ];}| process-compose condition | Native equivalent |
|---|---|
process_started |
"devenv:processes:X@started" |
process_healthy |
"devenv:processes:X@ready" or "devenv:processes:X" (default for processes; requires a ready probe on X) |
process_completed_successfully |
"devenv:processes:X@succeeded" |
process_completed |
"devenv:processes:X@completed" |
Restart policy
Section titled “Restart policy”{ processes.api.process-compose = { availability = { restart = "on_failure"; backoff_seconds = 2; max_restarts = 5; }; };}{ processes.api.restart = { on = "on_failure"; # "never" | "always" | "on_failure" max = 5; window = null; # optional: sliding window in seconds for rate limiting };}Note: backoff_seconds has no native equivalent. The native manager restarts immediately.
Environment variables and working directory
Section titled “Environment variables and working directory”{ processes.api.process-compose = { environment = [ "NODE_ENV=production" "PORT=3000" ]; working_dir = "/app"; };}{ processes.api = { env = { NODE_ENV = "production"; PORT = "3000"; }; cwd = "/app"; };}Readiness probes
Section titled “Readiness probes”The ready option works with both managers, so if you already use it, no changes are needed. If you used process-compose.readiness_probe directly:
{ processes.api.process-compose = { readiness_probe = { exec.command = "curl -f http://localhost:8080/health"; period_seconds = 5; failure_threshold = 3; }; };}{ processes.api.ready = { exec = "curl -f http://localhost:8080/health"; period = 5; failure_threshold = 3; };}The native manager also supports HTTP probes and sd_notify:
{ # HTTP probe processes.api.ready.http.get = { port = 8080; path = "/health"; };
# sd_notify: process sends READY=1 processes.app.ready.notify = true;}Liveness probes
Section titled “Liveness probes”process-compose supports liveness_probe separately from readiness_probe. The native manager has no liveness probe — use watchdog as an alternative for long-running health monitoring:
{ processes.api.process-compose = { liveness_probe = { exec.command = "check-alive"; period_seconds = 30; }; };}{ processes.api = { ready.notify = true; watchdog = { usec = 30000000; # 30 seconds in microseconds require_ready = true; }; };}The watchdog requires the process to send periodic WATCHDOG=1 heartbeats via NOTIFY_SOCKET. If your process doesn’t support sd_notify, wrap it:
# In your exec script:while true; do systemd-notify WATCHDOG=1; sleep 10; done &exec myappShutdown signal
Section titled “Shutdown signal”{ processes.postgres.process-compose = { shutdown.signal = 2; # SIGINT };}Set the signal on the process itself. The setting applies to both the native manager and process-compose:
{ processes.postgres.shutdown.signal = 2; # SIGINT}!!! tip “New in version 2.2.3”
`processes.<name>.shutdown` was added in devenv 2.2.3.Older 2.x versions send SIGTERM; wrap the process in a script that translates the signal if you need a different one.Elevated processes
Section titled “Elevated processes”{ processes.server.process-compose = { is_elevated = true; };}For specific privilege needs, use Linux capabilities instead:
{ processes.server.linux.capabilities = [ "net_bind_service" ];}git-hooks input is now optional
Section titled “git-hooks input is now optional”The git-hooks input is no longer included by default. If you use git-hooks.hooks in your devenv.nix, add the input explicitly:
inputs: git-hooks: url: github:cachix/git-hooks.nixIf you don’t use git-hooks, no changes are needed.
The pre-commit-hooks to git-hooks alias has also been removed.
If you often switch between devenv v1.x and v2.x, add the pre-commit-hooks input as well to prevent lockfile changes when switching versions:
inputs: pre-commit-hooks: follows: git-hookspre-commit renamed to prek
Section titled “pre-commit renamed to prek”The pre-commit command has been replaced by prek, a Rust rewrite. If you invoke pre-commit directly in scripts or shell commands, update them to use prek instead:
# Beforepre-commit run --all-files
# Afterprek run --all-filesdevenv build returns JSON
Section titled “devenv build returns JSON”devenv build now outputs JSON instead of plain store paths:
$ devenv build languages.rust.package{ "languages.rust.package": "/nix/store/...-rust-1.83.0"}Update any scripts that parse the output. For example, if you previously did:
store_path=$(devenv build languages.rust.package)Use jq to extract the value:
store_path=$(devenv build languages.rust.package | jq -r '.["languages.rust.package"]')devenv container subcommand cleanup
Section titled “devenv container subcommand cleanup”devenv container --copy <name> has been removed. Use the subcommand form instead:
$ devenv container copy <name>
