# 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

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:

**devenv.nix**

```nix
{
  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](/processes/) for details.

If there’s something process-compose does that the native manager doesn’t yet cover, please [let us know](https://github.com/cachix/devenv/issues).

### Migrating process-compose options

If you used `processes.<name>.process-compose` attributes, here’s how to translate them to native equivalents.

#### Dependencies

process-compose uses `depends_on` with conditions. The native manager uses `after` with lifecycle suffixes:

**Before**

```nix
{
  processes.api.process-compose = {
    depends_on.postgres.condition = "process_healthy";
    depends_on.migrations.condition = "process_completed_successfully";
    depends_on.cleanup.condition = "process_completed";
  };
}
```

**After**

```nix
{
  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

**Before**

```nix
{
  processes.api.process-compose = {
    availability = {
      restart = "on_failure";
      backoff_seconds = 2;
      max_restarts = 5;
    };
  };
}
```

**After**

```nix
{
  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

**Before**

```nix
{
  processes.api.process-compose = {
    environment = [ "NODE_ENV=production" "PORT=3000" ];
    working_dir = "/app";
  };
}
```

**After**

```nix
{
  processes.api = {
    env = {
      NODE_ENV = "production";
      PORT = "3000";
    };
    cwd = "/app";
  };
}
```

#### 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:

**Before**

```nix
{
  processes.api.process-compose = {
    readiness_probe = {
      exec.command = "curl -f http://localhost:8080/health";
      period_seconds = 5;
      failure_threshold = 3;
    };
  };
}
```

**After**

```nix
{
  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:

**Native-only probe types**

```nix
{
  # HTTP probe
  processes.api.ready.http.get = { port = 8080; path = "/health"; };


  # sd_notify: process sends READY=1
  processes.app.ready.notify = true;
}
```

#### 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:

**Before**

```nix
{
  processes.api.process-compose = {
    liveness_probe = {
      exec.command = "check-alive";
      period_seconds = 30;
    };
  };
}
```

**After**

```nix
{
  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:

```bash
# In your exec script:
while true; do systemd-notify WATCHDOG=1; sleep 10; done &
exec myapp
```

#### Shutdown signal

**Before**

```nix
{
  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:

**After**

```nix
{
  processes.postgres.shutdown.signal = 2;  # SIGINT
}
```

**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

**Before**

```nix
{
  processes.server.process-compose = {
    is_elevated = true;
  };
}
```

For specific privilege needs, use Linux capabilities instead:

**After**

```nix
{
  processes.server.linux.capabilities = [ "net_bind_service" ];
}
```

On first launch, devenv shows the requested capabilities and authenticates with `sudo`. The service itself still runs as your user; only the listed Linux capabilities are retained. In non-interactive environments, authenticate with `sudo -v` before running `devenv up`. A privileged broker remains available for the manager’s lifetime, so detached processes and automatic restarts remain non-interactive.

## 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:

**devenv.yaml**

```yaml
inputs:
  git-hooks:
    url: github:cachix/git-hooks.nix
```

If 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:

```yaml
inputs:
  pre-commit-hooks:
    follows: git-hooks
```

## `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:

```bash
# Before
pre-commit run --all-files


# After
prek run --all-files
```

## `devenv build` returns JSON

`devenv build` now outputs JSON instead of plain store paths:

```sh
$ 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:

```bash
store_path=$(devenv build languages.rust.package)
```

Use `jq` to extract the value:

```bash
store_path=$(devenv build languages.rust.package | jq -r '.["languages.rust.package"]')
```

## `devenv container` subcommand cleanup

`devenv container --copy <name>` has been removed. Use the subcommand form instead:

```sh
$ devenv container copy <name>
```