# Quickstart

> Configure initial defaults with devvm setup, launch a project VM with auto-detected toolchains and agents, and verify isolation in the guest shell.

- 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

- `internal/cli/up.go`
- `internal/cli/setup.go`
- `internal/vm/up.go`
- `internal/vm/detect.go`
- `internal/vm/parts.go`
- `internal/vm/start.go`

---

---
title: "Quickstart"
description: "Configure initial defaults with devvm setup, launch a project VM with auto-detected toolchains and agents, and verify isolation in the guest shell."
---

`devvm` (the root command, implemented by `Launch.Up` in `internal/vm/up.go`) provisions an isolated, per-folder virtual machine cloned from a shared base image, mounts the host project directory read-write with `.git` protection locked, mounts shared EROFS toolchain and coding agent images read-only at `/opt/devvm/tools`, starts a per-VM host HTTP/TLS proxy (`devvm _proxy`), and replaces the CLI process with an interactive guest shell at the matching project path. Global defaults for the base image features and new VM resources, network tier, coding agents, and layers are persisted in `~/.local/share/devvm/settings.json` via `devvm setup` or overridden declaratively in `~/.config/devvm/config.toml`.

## Prerequisites

Before creating a project VM, verify that the host hypervisor, Lima runtime (`limactl`), and filesystem permissions are ready:

```sh
devvm doctor
```

<Check>
`devvm doctor` inspects host virtualization support, Lima installation, `~/.ssh/config` integration, and state directory permissions without installing host software unprompted. If `devvm` is not yet installed, see [Installation](/installation).
</Check>

## Configure initial defaults with `devvm setup`

Running `devvm setup` configures the global defaults saved to `~/.local/share/devvm/settings.json`. If you run `devvm` in a project folder before running `devvm setup`, `creation.firstRun` automatically prompts for default VM size (`firstRunSize`), default coding agents (`firstRunAgents`), and `base` image features (`build.Choice.Choose`) before displaying the new VM plan.

### Interactive and non-interactive setup

In an interactive terminal without flags, `devvm setup` prompts for five settings in order, starting from the current effective defaults (`settings.Read`):

1. **Base image features** (`features`): Optional shell and CLI utilities built into `base` (`cli-tools`, `fzf`, `omb`). Until explicitly chosen, all three features are enabled by default.
2. **Default VM size** (`size.cpus`, `size.memory`): Default CPU count and memory in GiB for new VMs (saved as `devvm size -g` saves them). Defaults to `base` fitted to the host: `min(4, hostCPUs)` CPUs and `8` GiB memory (or `hostGiB / 2` when `8 + 2 > hostGiB`), with a `100` GiB minimum disk.
3. **Default network tier** (`network`): Egress confinement mode for new VMs (`allowlist`, `internet`, `open`, or `none`; defaults to `allowlist`).
4. **Default coding agents** (`agents`): Shared coding agents mounted into every new VM (`claude`, `codex`, `opencode`, or none).
5. **Default image layers** (`layers`): Built-in (`docker`, `nix`, `desktop`) or custom layers from `~/.config/devvm/layers/` stacked bottom-first onto `base` for every new VM.

<CodeGroup>
```sh title="Interactive terminal prompts"
devvm setup
```

```sh title="Non-interactive flag configuration"
devvm setup --features cli-tools,fzf,omb --agents claude,codex --layers none
```

```sh title="Minimal base image with no extras"
devvm setup --features none --agents none --layers none
```
</CodeGroup>

### `devvm setup` options and base features

Passing `--features`, `--agents`, or `--layers` applies the specified comma-separated list (or `none`) immediately without opening interactive pickers, leaving unspecified settings unchanged.

<ParamField body="--features" type="string">
  Comma-separated list of `base` image features (`cli-tools`, `fzf`, `omb`) or `none`. Rejects empty strings (`--features ''`). Changing features marks `base` and dependent layer images for rebuild on next use.
</ParamField>

<ParamField body="--agents" type="string">
  Comma-separated list of coding agents (`claude`, `codex`, `opencode`) or `none` that new VMs receive by default. Existing VMs keep their current agents.
</ParamField>

<ParamField body="--layers" type="string">
  Comma-separated, bottom-first list of layers (`docker`, `nix`, `desktop`, or user layers in `~/.config/devvm/layers/`) or `none` that new VMs receive by default.
</ParamField>

| Component type | Identifier | Contents and behavior |
| --- | --- | --- |
| Base feature | `cli-tools` | `bat`, `fd`, `eza`, `zoxide` (`z`), `btop`, `tree`, `tldr` (`tealdeer`) |
| Base feature | `fzf` | `fzf` package with `Ctrl-R` history and `Ctrl-T` file search wired into `~/.bashrc.d/fzf.sh` |
| Base feature | `omb` | Pinned `oh-my-bash` commit with themed prompt, completions, and aliases |
| Coding agent | `claude` | Claude Code shared EROFS tool image symlinked at `~/.local/bin/claude` |
| Coding agent | `codex` | OpenAI Codex CLI shared EROFS tool image with file-backed auth store and analytics disabled |
| Coding agent | `opencode` | opencode CLI shared EROFS tool image with placeholder auth import |
| Built-in layer | `docker` | Docker Engine (`dockerd`) inside the VM with container proxy configuration |
| Built-in layer | `nix` | Multi-user `nix-daemon` with the host-shared Nix binary cache (`http://devvm.proxy/nix`) |
| Built-in layer | `desktop` | XFCE desktop environment and VNC server reachable via `devvm desktop` |

<Note>
Settings defined in `~/.config/devvm/config.toml` take precedence over `~/.local/share/devvm/settings.json`. When a key is set in `config.toml`, `devvm setup` skips its interactive prompt and refuses any flag that attempts to overwrite that key (`set_in_config`).
</Note>

## Launch a project VM

Running `devvm` with no subcommand executes `Launch.Up`. For a folder that does not yet have a VM, `creation.plan` inspects the directory, merges defaults and flags, builds any missing base, layer, or shared tool images, creates the VM, and opens a shell.

```mermaid
sequenceDiagram
    autonumber
    actor User
    participant CLI as devvm (Launch.Up)
    participant Plan as creation.plan / detectFiles
    participant Build as build.Builds & tools.Versions
    participant Starter as starter.bringUp & Proxy
    participant Guest as VM (guest/sync.sh)

    User->>CLI: devvm (in ~/some-project)
    CLI->>Plan: Acquire folder creation lock & inspect project
    Plan->>Plan: Run firstRun (if unconfigured) & detectFiles(dir)
    Plan-->>User: Display summary & ask "Customize it? [y/N]"
    User-->>Plan: Confirm (Enter)
    CLI->>Build: Build base/layer image chain & shared tool .erofs images
    CLI->>CLI: Write state to vms/<hash>/vm.json & b.CreateVM
    CLI->>Starter: Lock .git protection, start devvm _proxy, b.Start
    Starter->>Starter: Run Proxy.SelfCheck & start port forwarder
    CLI->>Guest: Stream tar payload & run guest/sync.sh
    Guest->>Guest: Mount /opt/devvm/tools, install CAs, set (devvm) prompt
    CLI->>Guest: Exec interactive shell in ~/some-project
```

### Auto-detected toolchains and project markers

When `--tools` is omitted, `detectFiles` (`internal/vm/detect.go`) scans the top-level entries of the project folder in a deterministic order and enables the corresponding shared toolchain for each matched file or glob pattern. If `.devcontainer/devcontainer.json` or `.devcontainer.json` is present (and `--no-devcontainer` is not passed), `devcontainers.prepare` also maps supported devcontainer features into the default toolchains, layers, and forwarded ports.

| Part | Type | Top-level marker files (`detectFiles`) | Included binaries (`image.SharedTools`) |
| --- | --- | --- | --- |
| `node` | Toolchain | `package.json`, `bun.lock`, `bun.lockb` | `node`, `pnpm`, `bun` |
| `python` | Toolchain | `pyproject.toml`, `requirements.txt`, `uv.lock`, `setup.py`, `Pipfile` | `python`, `uv` |
| `rust` | Toolchain | `Cargo.toml` | `rust` (`rustup`, `cargo`, `rustc`) |
| `go` | Toolchain | `go.mod` | `go` |
| `dotnet` | Toolchain | `*.sln`, `*.slnx`, `*.csproj`, `*.fsproj`, `global.json` | `dotnet` |
| `nix` | Layer (picker hint) | `flake.nix`, `shell.nix`, `devenv.nix`, or `default.nix` containing `mkShell` | Annotates `nix` in the interactive picker with the marker filename; does not auto-enable the layer unless selected or required by `devcontainer.json` |

### Step-by-step first launch

<Steps>
  <Step title="Run devvm in a project directory">
    Change into your project directory and run `devvm`:

    ```sh
    cd ~/some-project
    devvm
    ```

    `devvm` refuses to mount unsafe directories—specifically your home directory (`~`), `~/.config/devvm`, `~/.local/share/devvm`, the Lima home directory, or any directory that contains them or sits inside `devvm`'s own directories—unless `--unsafe-folder` is explicitly passed or `--clone` is used.
  </Step>

  <Step title="Review and confirm the VM summary">
    Before building images or allocating a disk, `creation.choose` prints the computed VM name (`devvm-<slug>-<14-hex-hash>`) and the exact configuration sources:

    ```text
    ==> This folder's new VM, devvm-some-project-71a99b1076d92c, gets:
      toolchain node, for package.json
      agent claude, from devvm setup
      layers: none
      size: 4 CPUs, 8 GiB of memory and a 100 GiB disk
      network tier: allowlist
      ports: all
    Customize it? [y/N]
    ```

    Press `Enter` (or `n`) to create the VM as shown, or answer `y` to open the interactive multi-section picker for toolchains, coding agents, layers, network tier, forwarded ports, CPUs, memory, and disk size. If command-line flags specify every configurable part, or if standard input is not a terminal, `devvm` proceeds without prompting.
  </Step>

  <Step title="Build images and start the guest">
    On the first run, `devvm` builds the `base` image (downloading and verifying the pinned Ubuntu cloud image if not cached) and any required layer or `.erofs` shared tool images, then creates and boots the VM:

    ```text
    ==> building image base (e9f517dddef1)
        a few minutes, and Ubuntu's cloud image to download the first time
        (details: ~/.local/share/devvm/logs/runs/20261024-090616-83541.log)
    ==> image base ready
    ==> creating devvm-some-project-71a99b1076d92c from base
    ==> starting devvm-some-project-71a99b1076d92c
    ```

    If you interrupt (`Ctrl+C`) before the first shell opens, `undoCreate` automatically deletes the unfinished VM, removes its state directory, and unlocks `.git` protection.
  </Step>

  <Step title="Authenticate coding agents on the host">
    If your VM includes a coding agent (`claude`, `codex`, or `opencode`), authenticate on the **host** machine (or import existing host credentials) so the host proxy can inject credentials in transit without storing real tokens inside the VM:

    <Tabs>
      <Tab title="Log in via OAuth or API key">
        Run `devvm agent login` in a host terminal inside the project directory:

        ```sh
        devvm agent login claude
        ```
      </Tab>
      <Tab title="Import existing host agent settings">
        Copy your host's existing agent settings and logins into `~/.config/devvm/agents/` and the host secret store:

        ```sh
        devvm agent import
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

### Root `devvm` (`up`) creation flags

Flags passed to `devvm` apply when creating a new VM (or select which folder's VM to open):

<ParamField body="--tools" type="string">
  Comma-separated list of toolchains (`node`, `python`, `rust`, `go`, `dotnet`) or `none`. Overrides auto-detection from project files.
</ParamField>

<ParamField body="--agents" type="string">
  Comma-separated list of coding agents (`claude`, `codex`, `opencode`) or `none`. Overrides the `devvm setup` / `config.toml` default.
</ParamField>

<ParamField body="--layers" type="string">
  Comma-separated, bottom-first list of layers (`docker`, `nix`, `desktop`, or custom layers) or `none`. Replaces the `devvm setup` default.
</ParamField>

<ParamField body="--network" type="string">
  Network tier for the new VM: `allowlist`, `internet`, `open`, or `none`. Defaults to `devvm network -g` / `config.toml`, else `allowlist`.
</ParamField>

<ParamField body="--cpus" type="string">
  Number of virtual CPUs (`N`) for the new VM. Must not exceed host CPU count.
</ParamField>

<ParamField body="--memory" type="string">
  Memory allocation in `GiB` (such as `16` or `16G`). Must not exceed `max(hostGiB - 2, hostGiB / 2)`.
</ParamField>

<ParamField body="--disk" type="string">
  Disk size in `GiB`, at least the base image size (`100` GiB). VM disks can grow later via `devvm size`, but never shrink.
</ParamField>

<ParamField body="--ports" type="string">
  Guest ports forwarded to host `127.0.0.1`: `all`, `none`, or a comma-separated list of ports and ranges such as `5173,8000-8010` (default `all`).
</ParamField>

<ParamField body="--clone" type="boolean">
  Create the VM with an independent Git clone inside the VM disk at the project path instead of mounting the host project folder. Mutually exclusive with `--parent`.
</ParamField>

<ParamField body="--no-git-protect" type="boolean">
  Create a folder-mounted VM without locking `.git` hooks and configuration on the host.
</ParamField>

<ParamField body="--ssh-agent" type="boolean">
  Forward your host SSH agent into the VM while connected so guest commands can use host SSH keys.
</ParamField>

<ParamField body="--from" type="string">
  Build the VM's base image from an OCI/Docker container image reference (such as `node:24` or `alpine:3.21`) instead of devvm's Ubuntu cloud image.
</ParamField>

<ParamField body="--no-devcontainer" type="boolean">
  Ignore `.devcontainer/devcontainer.json` and `.devcontainer.json` when planning the VM.
</ParamField>

<ParamField body="--here" type="boolean">
  Create or open a dedicated VM for the current subdirectory rather than reusing an ancestor directory's VM.
</ParamField>

<ParamField body="--parent" type="boolean">
  Open an ancestor directory's existing VM in the current subdirectory even when this subdirectory also has a VM.
</ParamField>

## Verify guest isolation in the VM shell

On every launch, `syncer.sync` (`internal/vm/sync.go`) streams an in-memory tar archive to the guest and executes `assets/guest/sync.sh` before opening your interactive shell. The shell prompt is prefixed with `(devvm)` via `~/.bashrc.d/devvm-prompt.sh` and starts in the exact host path (`~/some-project`).

Run the following checks inside the `(devvm)` guest shell to verify each isolation boundary:

<Steps>
  <Step title="Verify filesystem containment and read-only .git protection">
    In a standard mounted VM, only the project folder is mounted read-write from the host; the rest of `~` resides on the VM's own virtual disk. On macOS, `gitprotect` locks `.git` files (`config`, `hooks`, `info`, `objects`, `refs`) with `uchg` user immutable flags on the host so a compromised guest process cannot install malicious Git hooks or alter repository config:

    ```sh
    pwd
    ls -la ..
    touch .git/hooks/pre-commit
    ```

    Expected behavior:
    - `pwd` prints the host project path (for example, `/Users/you/some-project`), while `ls -la ..` shows only the parent path stub on the VM root filesystem—none of your sibling host directories or host `~` dotfiles exist in the guest.
    - `touch .git/hooks/pre-commit` fails with `Operation not permitted` (or `Read-only file system`).
  </Step>

  <Step title="Verify read-only EROFS shared toolchains and agents">
    Shared toolchains and coding agents are mounted from host `.erofs` images into `/opt/devvm/tools/<tool>/<version>` by `/usr/local/sbin/devvm-tools` and wired into `/etc/mise/config.toml`:

    ```sh
    df -T /opt/devvm/tools/*/*
    mise ls
    ```

    Expected behavior:
    - Each active tool (`node`, `python`, `rust`, `go`, `dotnet`, `claude`, `codex`, `opencode`) is mounted as a read-only `erofs` filesystem under `/opt/devvm/tools/`.
    - Global package installs or tool self-updates cannot mutate the shared image; `go install` writes to `~/go/bin` (`go_set_gobin = false`) and npm global installs write to `~/.npm-global`.
  </Step>

  <Step title="Verify network confinement and devvm-why">
    On the default `allowlist` tier, guest traffic is confined to the host proxy (`$HTTP_PROXY` / `$HTTPS_PROXY`), which permits only hosts in the base, toolchain, agent, layer, global, and per-VM allowlists. Test an unallowed external host and inspect the block report with `devvm-why`:

    ```sh
    curl -I https://example.com
    devvm-why
    ```

    Expected behavior:
    - The request to `https://example.com` is rejected by the proxy (`403 Forbidden`), and the `_devvm_blocked` prompt hook (or running `devvm-why`) prints the blocked host along with the exact host command required to permit it: `devvm network allow example.com`.
  </Step>

  <Step title="Verify secret placeholder substitution">
    Inspect the guest environment and agent login files to confirm that no plaintext tokens exist inside the VM:

    ```sh
    cat ~/.config/devvm/env
    ```

    Expected behavior:
    - Secret variables (such as `CLAUDE_CODE_OAUTH_TOKEN` or `GH_TOKEN`) and agent auth files (`~/.claude.json`, `~/.codex/auth.json`) contain salted placeholder strings rather than real credentials. The host proxy substitutes the real secret only over TLS connections to the bound destination host signed by the VM's proxy CA (`/etc/ssl/devvm.pem`).
  </Step>
</Steps>

## Everyday workspace lifecycle commands

Once a project VM exists, subsequent `devvm` invocations in the project folder (or any of its subdirectories) reuse and start the existing VM in seconds:

```sh
devvm                   # Open a shell in this folder's VM, starting it if stopped
devvm --clone           # Create a VM with an isolated repo clone instead of a folder mount
devvm tools             # Interactively add or remove shared toolchains and coding agents
devvm code              # Open the project inside the VM in VS Code over Remote-SSH
devvm stop              # Stop this folder's VM and its host proxy
devvm ls                # List all project VMs, their status, image, and network tier
devvm rm                # Delete this folder's VM after confirmation, exporting agent history first
```

## Next

<CardGroup cols={2}>
  <Card title="VM lifecycle and state" href="/vm-lifecycle-and-state">
    How project paths map to VM names and hashes, vm.json state sections, and in-place vs. remake updates.
  </Card>
  <Card title="Configure coding agents and credentials" href="/coding-agents-and-credentials">
    Authenticate Claude Code, Codex, and opencode, import and save agent settings, and bind host secrets.
  </Card>
  <Card title="Network tiers and host proxy" href="/network-tiers-and-proxy">
    Traffic confinement across allowlist, internet, open, and none tiers, plus devvm _proxy inspection.
  </Card>
  <Card title="Host and Git isolation boundaries" href="/host-and-git-isolation">
    Read-only .git mount protection, unsafe folder checks, secret placeholder injection, and --clone mode.
  </Card>
</CardGroup>
