# Installation

> Install devvm on macOS and Linux via Homebrew or the signed release installer, verify Minisign signatures, and validate host readiness with devvm doctor.

- Repository: devvmhq/devvm
- GitHub: https://github.com/devvmhq/devvm
- Human docs: https://grok-wiki.com/public/docs/devvmhq-devvm-4242986d2853
- Complete Markdown: https://grok-wiki.com/public/docs/devvmhq-devvm-4242986d2853/llms-full.txt

## Source Files

- `install.sh`
- `packaging/homebrew/devvm.rb`
- `packaging/key.go`
- `internal/cli/doctor.go`
- `internal/upgrade/minisign.go`
- `internal/backend/lima/check.go`

---

---
title: "Installation"
description: "Install devvm on macOS and Linux via Homebrew or the signed release installer, verify Minisign signatures, and validate host readiness with devvm doctor."
---

`devvm` ships as a static Go binary (`CGO_ENABLED=0`) with all guest provisioning scripts, base Lima templates (`assets/base.yaml`), built-in layer definitions, and default network policies embedded directly into the executable via `assets.FS`. You can install `devvm` on macOS and Linux (`arm64` and `amd64`) through the `devvmhq/tap` Homebrew tap or the standalone `install.sh` release script, verify release artifacts against the maintainer's Ed25519 Minisign public key, and validate hypervisor and host readiness with `devvm doctor`.

## Host requirements

`devvm` runs virtual machines through its `lima` backend (`internal/backend/lima`). Every command that interacts with VMs runs preflight host checks (`host.Problems`) and stops immediately if a required host capability is missing, without installing host software unprompted.

<Tabs>
  <Tab title="macOS">
    | Requirement | Minimum / Value | Verification command | Automated by `install.sh` |
    | --- | --- | --- | --- |
    | Operating system | macOS 13 or newer (`minMacOS = "13"`) | `sw_vers -productVersion` | No |
    | Architecture | `arm64` (Apple silicon) or `amd64` (Intel) | `uname -m` | No |
    | Lima | Lima 2.2 or newer (`minLima = "2.2"`) | `limactl --version` | Yes (`brew install lima` or tarball into `$DEVVM_PREFIX`) |
    | Recommended resources | 4 CPUs, 8 GiB RAM, 8 GiB free disk | `sysctl hw.ncpu hw.memsize` | No (warns only; smaller hosts scale down base VM size) |
    | OpenSSH | OpenSSH 8.7+ (included in macOS 13+) | `ssh -V` | No (required for `ssh <vm-name>` and editor integration) |
    | Docker Desktop | Optional | `docker version` | No (only required for `--from` with local-only Docker images) |
  </Tab>
  <Tab title="Linux">
    | Requirement | Minimum / Value | Verification command | Automated by `install.sh` |
    | --- | --- | --- | --- |
    | Operating system | Linux (`arm64` / `aarch64` or `amd64` / `x86_64`) | `uname -s && uname -m` | No |
    | Lima | Lima 2.2 or newer (`minLima = "2.2"`) | `limactl --version` | Yes (`brew install lima` or tarball into `$DEVVM_PREFIX`) |
    | QEMU | `qemu-system-aarch64` or `qemu-system-x86_64`, plus `qemu-img` | `qemu-img --version` | Yes (`apt-get`, `dnf`, `pacman`, or `zypper` via `sudo`) |
    | KVM device access | Read/write access to `/dev/kvm` | `test -r /dev/kvm -a -w /dev/kvm` | Yes (`sudo usermod -aG kvm $USER`; requires re-login) |
    | Recommended resources | 4 CPUs, 8 GiB RAM, 8 GiB free disk | `nproc && free -g` | No (warns only; smaller hosts scale down base VM size) |
    | OpenSSH | OpenSSH 8.7+ | `ssh -V` | No (required for `ssh <vm-name>` and editor integration) |
    | Docker Engine | Optional | `docker version` | No (only required for `--from` with local-only Docker images) |
  </Tab>
</Tabs>

<Note>
On Apple silicon Macs running an `x86_64` shell under Rosetta, `install.sh` queries `sysctl -in hw.optional.arm64` and installs the native `darwin-arm64` binary.
</Note>

## Install `devvm`

Install `devvm` using either Homebrew or the release installer script. Do not install both simultaneously; having two copies on `PATH` can cause `brew upgrade` to update the inactive binary while the other remains first on `PATH`.

<Tabs>
  <Tab title="Homebrew (macOS and Linux)">
    The `devvmhq/tap` repository provides two mutually exclusive formulas built from `packaging/homebrew/devvm.rb`:
    - `devvm`: tracks stable releases (`x.y.z`).
    - `devvm-rc`: tracks the newest release or prerelease (`x.y.z-rc.N`, `x.y.z-beta.N`).

    Both formulas declare `depends_on "lima"`, installing Lima automatically if it is not already present.

    ```bash
    brew trust --formula devvmhq/tap/devvm
    brew install devvmhq/tap/devvm
    ```

    To follow release candidates and betas instead of stable releases:

    ```bash
    brew trust --formula devvmhq/tap/devvm-rc
    brew install devvmhq/tap/devvm-rc
    ```
  </Tab>
  <Tab title="Release installer (Linux and macOS)">
    The standalone `install.sh` script requires `curl`, `tar`, and either `sha256sum` or `shasum`. It installs `devvm` into `~/.local/lib/devvm/<version>` and symlinks `~/.local/bin/devvm` without requiring `sudo`.

    ```bash
    curl -fsSL https://github.com/devvmhq/devvm/releases/latest/download/install.sh | bash
    ```

    Pass flags to `install.sh` after `bash -s --`:

    ```bash
    curl -fsSL https://github.com/devvmhq/devvm/releases/latest/download/install.sh | bash -s -- --yes
    ```
  </Tab>
</Tabs>

### Installer options and environment variables

`install.sh` accepts the following command-line flags and environment variables:

<ParamField body="--yes, -y" type="boolean" default="false">
  Answer yes to every interactive dependency repair prompt (`lima`, `qemu`, and `kvm-access`). Required when running non-interactively without a controlling `/dev/tty` if host dependencies are missing.
</ParamField>

<ParamField body="--version" type="string">
  Install a specific release or prerelease tag (for example, `0.1.0` or `0.2.0-rc.1`) instead of resolving the latest release. Equivalent to setting `DEVVM_VERSION`.
</ParamField>

<ParamField body="--no-deps" type="boolean" default="false">
  Install only the `devvm` binary archive and skip running `devvm doctor` dependency checks and remediation prompts.
</ParamField>

<ParamField body="--force" type="boolean" default="false">
  Bypass the Homebrew collision check (`check_brew`) and install a second copy of `devvm` even when a Homebrew keg exists in `Cellar/devvm` or `Cellar/devvm-rc`.
</ParamField>

<ParamField body="DEVVM_PREFIX" type="string" default="~/.local">
  Base installation directory. `install.sh` extracts release archives to `$DEVVM_PREFIX/lib/devvm/<version>` and links the executable at `$DEVVM_PREFIX/bin/devvm`.
</ParamField>

<ParamField body="DEVVM_VERSION" type="string">
  Target release version to install when `--version` is not passed on the command line.
</ParamField>

<ParamField body="DEVVM_REPO_URL" type="string" default="https://github.com/devvmhq/devvm">
  Base GitHub repository URL used to resolve `/releases/latest` and download release archives.
</ParamField>

<ParamField body="DEVVM_LIMA_URL" type="string" default="https://github.com/lima-vm/lima">
  Base GitHub repository URL used when downloading Lima release tarballs on hosts without Homebrew.
</ParamField>

### Installer execution flow and filesystem layout

When `install.sh` runs, it performs five sequential operations:

<Steps>
  <Step title="Check host tools and existing Homebrew installations">
    `check_host` verifies that the OS/architecture pair is supported (`darwin` or `linux` on `arm64` or `amd64`) and checks `PATH` for `curl`, `tar`, and `sha256sum` or `shasum`. Next, `check_brew` inspects `HOMEBREW_CELLAR`, `HOMEBREW_PREFIX/Cellar`, `<brew-dir>/../Cellar`, `/opt/homebrew/Cellar`, `/usr/local/Cellar`, and `/home/linuxbrew/.linuxbrew/Cellar` for `devvm` or `devvm-rc` keg directories without executing `brew`. If a keg exists and `--force` is not set, `install.sh` exits with status `1`.
  </Step>
  <Step title="Resolve version and verify cryptographic signatures">
    `resolve_version` determines the target version from `--version` / `DEVVM_VERSION`, the `release_version` stamped into the release's `install.sh`, or the HTTP redirect target of `$DEVVM_REPO_URL/releases/latest` (rejecting prerelease tags on `releases/latest`). It downloads `devvm-<version>-<os>-<arch>.tar.gz`, `SHA256SUMS`, and `SHA256SUMS.minisig` over HTTPS (`--proto =https --proto-redir =https`), verifies the Minisign signature when `minisign` is installed, and compares the archive's SHA-256 digest against `SHA256SUMS`.
  </Step>
  <Step title="Stage and atomically link the binary">
    `install_devvm` extracts the archive into a temporary directory, moves it to `$DEVVM_PREFIX/lib/devvm/.<version>.new.$$`, points `$DEVVM_PREFIX/bin/devvm` at the staged binary, replaces `$DEVVM_PREFIX/lib/devvm/<version>`, and updates the symlink to `$DEVVM_PREFIX/lib/devvm/<version>/devvm`. Older version directories under `$DEVVM_PREFIX/lib/devvm/` are removed unless `ps -A -ww -o command=` shows a running process (such as a VM's `devvm _proxy` or `devvm _qemu` wrapper) still executing from that path.
  </Step>
  <Step title="Offer host dependency fixes">
    Unless `--no-deps` is passed, `install_deps` runs `devvm doctor --json`, parses the `problems` array with `awk`, and prompts on `/dev/tty` (or auto-accepts with `--yes`) to install missing dependencies (`lima`, `qemu`, or `kvm-access`).
  </Step>
  <Step title="Verify PATH order">
    `path_hint` checks whether `$DEVVM_PREFIX/bin` is in `PATH`. If missing, it prints the `export PATH` snippet for `~/.zshrc` or `~/.bashrc`. If present, it resolves `command -v devvm` with `readlink -f` and warns if another `devvm` binary precedes `$DEVVM_PREFIX/bin/devvm`.
  </Step>
</Steps>

:::files
~/.local/
├── bin/
│   ├── devvm -> ~/.local/lib/devvm/<version>/devvm
│   └── limactl                                     # Present when Lima is installed via tarball
├── lib/
│   └── devvm/
│       └── <version>/
│           ├── devvm                               # Static Go binary with embedded assets
│           ├── README.md
│           ├── CHANGELOG.md
│           └── LICENSE
└── share/
    ├── devvm/                                      # Runtime state, images, VMs, and Lima home (created on first run)
    └── lima/                                       # Present when Lima is installed via tarball
:::

## Release signing and verification

Every release is built from a signed Git tag verified against `packaging/release-signer` by `scripts/release.sh`. Before publishing, `scripts/release.sh` stamps `release_version` and `minisign_key` into `dist/install.sh`, appends the SHA-256 checksum of `install.sh` to `SHA256SUMS`, and signs `SHA256SUMS` with Minisign using the trusted comment `devvm <version>`.

```mermaid
flowchart TD
  subgraph Repo["Repository & Build (scripts/release.sh)"]
    PubKey["packaging/minisign.pub<br/>Key ID: 8ADA44EC275BCCA3"]
    Embed["packaging/key.go<br/>//go:embed *"]
    PubKey --> Embed
  end

  subgraph Release["GitHub Release Assets"]
    Script["install.sh<br/>(stamped release_version & minisign_key)"]
    Sums["SHA256SUMS<br/>(archives + install.sh digests)"]
    Sig["SHA256SUMS.minisig<br/>(ED BLAKE2b-512 + global signature)"]
    Archive["devvm-&lt;version&gt;-&lt;os&gt;-&lt;arch&gt;.tar.gz"]
  end

  subgraph Tap["devvmhq/homebrew-tap"]
    Formula["Formula/devvm.rb & devvm-rc.rb<br/>(pinned platform URLs & SHA-256)"]
  end

  PubKey -. "verifies" .-> Sig
  Sig -- "authenticates" --> Sums
  Sums -- "verifies SHA-256" --> Script
  Sums -- "verifies SHA-256" --> Archive
  Formula -- "verifies SHA-256 out-of-band" --> Archive
  Embed -- "compiled into devvm binary for devvm upgrade" --> Archive
```

### Embedded Minisign public key

The public key in `packaging/minisign.pub` (embedded into the `devvm` binary by `packaging/key.go`) is:

```text
untrusted comment: minisign public key 8ADA44EC275BCCA3
RWSjzFsn7ETaijKJ2y7F4DtZobXmyv+LmdclJwZP/KmAkLUwOCzWF/2b
```

### Verify `install.sh` before execution

Piping `curl ... | bash` relies on the `install.sh` served by GitHub Releases to carry the public key. To eliminate trust in the release server before executing any shell code, verify `SHA256SUMS` and `install.sh` manually against the public key above:

<Steps>
  <Step title="Download the installer, checksum manifest, and signature">
    ```bash
    base=https://github.com/devvmhq/devvm/releases/latest/download
    curl -fsSL -O "$base/install.sh" -O "$base/SHA256SUMS" -O "$base/SHA256SUMS.minisig"
    ```
  </Step>
  <Step title="Verify the Minisign signature on SHA256SUMS">
    Requires `minisign` (`brew install minisign` or your Linux package manager):

    ```bash
    minisign -Vm SHA256SUMS -P RWSjzFsn7ETaijKJ2y7F4DtZobXmyv+LmdclJwZP/KmAkLUwOCzWF/2b
    ```

    Expected output:

    ```text
    Signature and comment signature verified
    Trusted comment: devvm <version>
    ```
  </Step>
  <Step title="Verify install.sh against the signed SHA256SUMS">
    <CodeGroup>
      ```bash macOS (shasum)
      shasum -a 256 -c --ignore-missing SHA256SUMS
      ```
      ```bash Linux (sha256sum)
      sha256sum -c --ignore-missing SHA256SUMS
      ```
    </CodeGroup>

    Expected output:

    ```text
    install.sh: OK
    ```
  </Step>
  <Step title="Run the verified script">
    ```bash
    bash install.sh
    ```
  </Step>
</Steps>

### Built-in signature verification during upgrades

While `install.sh` delegates signature verification to the external `minisign` CLI if installed (and falls back to SHA-256 verification with a warning if `minisign` is absent), `devvm upgrade` verifies signatures natively in Go (`internal/upgrade/minisign.go` and `internal/upgrade/source.go`) using the key compiled into the running binary (`packaging.MinisignKey()`):

1. **Algorithm enforcement**: `PublicKey.Verify` accepts only prehashed `ED` signatures (Ed25519 over the BLAKE2b-512 digest of `SHA256SUMS`) and rejects legacy unhashed `Ed` signatures.
2. **Key ID matching**: The 8-byte key ID in the signature header must match `8ADA44EC275BCCA3`.
3. **Global signature and downgrade protection**: It verifies the Ed25519 global signature over `signature || trusted_comment` and requires the trusted comment to equal `devvm <version>` exactly, preventing a valid `SHA256SUMS` from an older release from being replayed for a different version.

## Validate host readiness with `devvm doctor`

Run `devvm doctor` to inspect backend capabilities, host requirements, proxy certificate configuration, and workspace health:

```bash
devvm doctor
```

`devvm doctor` exits with code `0` when all backend requirements are satisfied (even if warnings are present) and exits with code `1` when one or more backend problems exist.

### Problem keys and automated fixes

`internal/backend/lima/check.go` evaluates the host and returns structured `backend.Problem` entries. Both `devvm doctor` and `install.sh` use these problem keys:

| Problem key | Condition checked | Suggested `fix` command | `install.sh` action |
| --- | --- | --- | --- |
| `os` | `runtime.GOOS` is neither `darwin` nor `linux` | `null` | Prints message |
| `arch` | `runtime.GOARCH` is neither `arm64` nor `amd64` | `null` | Prints message |
| `macos` | macOS major version is below `13` | `null` | Prints message |
| `lima` | `limactl` is missing from `PATH`, fails `limactl --version`, or is older than `2.2` | With `brew` on `PATH`: `brew install lima`, `brew reinstall lima`, or `brew upgrade lima`. Without `brew`: `null` | Prompts to run `brew` fix, or downloads and verifies the latest Lima release tarball into `$DEVVM_PREFIX` |
| `qemu` | On Linux, `qemu-system-aarch64` (`arm64`) or `qemu-system-x86_64` (`amd64`) or `qemu-img` is missing | First package manager found on `PATH` (see table below), or `null` | Prompts to run `fix` via `/dev/tty` (`sudo`) |
| `kvm` | On Linux, `/dev/kvm` does not exist (`os.Stat` fails) | `null` (enable virtualization in BIOS/UEFI or host hypervisor) | Prints message |
| `kvm-access` | On Linux, `/dev/kvm` exists but `os.OpenFile("/dev/kvm", os.O_RDWR, 0)` fails | `sudo usermod -aG kvm <USER>` | Prompts to run `fix`, then instructs you to log out and back in |
| `lima-config` | `~/.local/share/devvm/lima/_config/default.yaml` or `override.yaml` exists | `null` (remove global config files from devvm's isolated Lima home) | Prints message |

On Linux hosts missing QEMU, `qemuInstall` in `internal/backend/lima/check.go` selects the install command for the first package manager found on `PATH`:

| Package manager | Architecture | Generated `fix` command |
| --- | --- | --- |
| `apt-get` | `amd64` | `sudo apt-get install -y --no-install-recommends qemu-utils ipxe-qemu qemu-system-x86 ovmf` |
| `apt-get` | `arm64` | `sudo apt-get install -y --no-install-recommends qemu-utils ipxe-qemu qemu-system-arm qemu-efi-aarch64` |
| `dnf` | `amd64` / `arm64` | `sudo dnf install -y qemu-kvm qemu-img` |
| `pacman` | `amd64` / `arm64` | `sudo pacman -S --noconfirm qemu-base` |
| `zypper` | `amd64` / `arm64` | `sudo zypper --non-interactive install qemu-kvm` |

### Non-fatal warnings and notices

`devvm doctor` also checks for non-fatal conditions. Warnings are printed to stderr (or collected in the `warnings` array with `--json`) and never cause `devvm doctor` to exit with a non-zero code:

- **Shadowed binary on `PATH`**: The first `devvm` found via `exec.LookPath("devvm")` resolves (`filepath.EvalSymlinks`) to a different file than `os.Executable()`.
- **Low disk space**: The volume hosting `~/.local/share/devvm/lima` has less free space than the base build floor (`8 GiB`, defined in ` Lima.Floors().Base`).
- **Undersized host**: Host RAM is below `8 GiB` (`recommendedMemoryGiB = 8`) or CPU count is below `4` (`recommendedCPUs = 4`).
- **Invalid network policy lines**: Lines in `~/.config/devvm/policy`, `~/.config/devvm/layers/*.policy`, or `~/.local/share/devvm/vms/*/policy` that are not valid rules (`netpolicy.NotRules`).
- **Stray entries in `vms/`**: Entries in `~/.local/share/devvm/vms/` that are not valid VM state directories (`state.VMFolderProblems`).
- **Corporate proxy CA notice**: When `HTTPS_PROXY` or `config.toml`'s `proxy.https` is configured and `~/.config/devvm/ca` contains no `.crt`/`.pem` certificates, `devvm doctor` prints an informational notice reminding you to place TLS-inspecting proxy root CAs in `~/.config/devvm/ca`.

### Machine-readable output (`devvm doctor --json`)

Passing `--json` outputs a versioned JSON payload to stdout and always exits `0` unless internal directory resolution fails.

<RequestExample>
```bash Command
devvm doctor --json
```
</RequestExample>

<ResponseExample>
```json Output
{
  "version": 1,
  "backend": "lima",
  "problems": [],
  "warnings": [],
  "docker": true,
  "features": {
    "lima": {
      "agent-folders": "native",
      "agent-forwarding": "native",
      "agent-toggle": "native",
      "build-confinement": "native",
      "build-image": "native",
      "clone": "native",
      "cpus": "native",
      "disk": "native",
      "docker-layer": "native",
      "egress-proxy": "native",
      "from": "native",
      "link": "native",
      "memory": "native",
      "nix-layer": "native",
      "ports": "emulated",
      "ports-all": "emulated",
      "project-mount": "native",
      "ssh": "native",
      "tools-share": "native"
    }
  },
  "release": {
    "latest": "0.1.0",
    "checked": "2026-11-07T12:00:00Z"
  }
}
```
</ResponseExample>

<ResponseField name="version" type="integer" required>
  JSON schema version, always `1`.
</ResponseField>

<ResponseField name="backend" type="string" required>
  Default backend used for new VMs (`"lima"`).
</ResponseField>

<ResponseField name="problems" type="array" required>
  Blocking backend requirements that are missing on this host. Each item contains `key` (`string`), `fix` (`string` or `null`), and `message` (`string`).
</ResponseField>

<ResponseField name="warnings" type="array" required>
  Non-blocking warning messages (`[]string`) for `PATH` shadowing, low disk space, fewer than 4 CPUs or 8 GiB RAM, malformed policy lines, or stray directories in `vms/`.
</ResponseField>

<ResponseField name="docker" type="boolean" required>
  Whether the `docker` CLI is present on `PATH` (used only when building `--from` a local Docker image not present in a remote registry).
</ResponseField>

<ResponseField name="features" type="object" required>
  Map of backend name to its 19 capability support levels (`"native"`, `"emulated"`, `"unreviewed"`, or `"none"`). On `lima`, `ports` and `ports-all` are `"emulated"` (reviewed and active via `vmssh.Forwards`), and all other 17 features are `"native"`.
</ResponseField>

<ResponseField name="release" type="object">
  Result of the most recent background daily release check (`~/.local/share/devvm/release-check.json`), containing `latest` (`string`) and `checked` (RFC 3339 UTC timestamp), or omitted/`null` if no check has completed yet.
</ResponseField>

## Next

<CardGroup cols={2}>
  <Card title="Quickstart" href="/quickstart">
    Configure initial defaults with `devvm setup`, launch your first project VM, and verify isolation inside the guest shell.
  </Card>
  <Card title="Diagnose host, boot, and SSH failures" href="/host-and-vm-diagnostics">
    Resolve `devvm doctor` host problems, KVM permissions, Ubuntu cloud image boot fallbacks, and SSH host-key errors.
  </Card>
  <Card title="Manage disk retention, garbage collection, and upgrades" href="/disk-retention-and-upgrades">
    Upgrade `devvm` installations with `devvm upgrade`, switch between stable and prerelease channels, or uninstall cleanly.
  </Card>
  <Card title="Pinning, release signing, and supply-chain verification" href="/release-and-supply-chain-pipeline">
    Inspect how `scripts/release.sh` verifies signed tags, builds release archives, and signs `SHA256SUMS` with Minisign.
  </Card>
</CardGroup>
