Skip to content

Latest commit

 

History

History
535 lines (382 loc) · 16.2 KB

File metadata and controls

535 lines (382 loc) · 16.2 KB

Getting Started

coop runs Claude Code, Codex, and Grok Build inside isolated virtual machines. On Linux, it spins up Firecracker microVMs backed by KVM. On macOS, it uses Lima with Apple's Virtualization.framework. Each VM gets its own filesystem, network stack, and Docker daemon. Agent CLIs never touch your host.

Prerequisites

macOS (Lima backend)

  • Lima installed with limactl on your PATH (brew install lima) — coop setup fails without it
  • Apple Silicon (arm64)
  • Rosetta 2 for x86_64 guests on Apple Silicon: softwareupdate --install-rosetta

Linux (Firecracker backend)

  • KVM access (/dev/kvm must exist and be writable by your user)
  • x86_64 or arm64 architecture (x86_64 is the primary test target; arm64 builds are available but untested)
  • sudo privileges (Firecracker uses jailer and TAP networking)
  • curl, tar, e2fsprogs (for mkfs.ext4, resize2fs)
  • Setup also checks for setfacl, unsquashfs, ssh, and rsync. Automatic installation of missing tools requires apt-get; on other hosts, install the packages providing the reported tools manually and rerun coop setup. See backend prerequisites.

Install

Install the latest release:

curl -fsSL https://raw.githubusercontent.com/trailofbits/coop/main/install.sh | bash

install.sh verifies the downloaded tarball's SHA-256 against the release's SHA256SUMS and, when the GitHub CLI is installed, also verifies its Sigstore build-provenance attestation. coop update runs the same verification, except that it treats the checksum as mandatory and refuses to install without it. To verify a tarball by hand, download attestations.jsonl from the same release and pass --bundle (this needs no GitHub credential — releases up to v0.5.4 predate the bundle asset and do not publish it):

gh attestation verify coop-<version>-<triple>.tar.gz --repo trailofbits/coop \
  --bundle attestations.jsonl

Dropping --bundle makes gh fetch the attestation from the GitHub API instead, which it will only do when gh is logged in. install.sh and coop update use that API path themselves for releases published without a usable bundle.

Upgrading from v0.5.4

Rerun the installer above when upgrading from v0.5.4 to a release that includes credential proxy support. The v0.5.4 updater replaces only coop; it does not install the new coop-proxy companion. Proxy mode requires both binaries in the same directory. If you installed into a custom directory, pass the same INSTALL_DIR to the installer.

The published v0.5.4 Linux ARM64 binary reports coop 0.5.4-dev (8e24729+dirty) and refuses coop update because it identifies itself as a development build. Rerunning the installer also bypasses that old updater.

Build from source

Install Rust and CMake, then:

cargo build --workspace --release

The binaries land at target/release/coop and target/release/coop-proxy. Keep them in the same directory when installing: proxy mode looks for its companion next to coop.

Nix

The flake supports native builds on macOS arm64 and Linux x86_64/arm64. It pins its inputs in flake.lock and reads the Rust version from rust-toolchain.toml.

The commands below require Nix 2.30 or newer, with the nix-command and flakes experimental features enabled. Check your version with nix --version. (nix profile add was introduced in Nix 2.30.) From a checkout:

nix build
./result/bin/coop --version
nix run . -- --help

result/bin contains both coop and coop-proxy. To install them in your Nix profile:

nix profile add .#coop

The explicit #coop output names the profile entry coop, independently of the checkout directory name. If you previously installed with nix profile add ., check nix profile list for the existing entry name and use that name for upgrade/removal, or remove that entry and reinstall with .#coop.

On macOS, the package includes Lima and the host command-line tools used by coop, so nix run . -- setup does not require a separate Homebrew install. The Apple Silicon and Rosetta prerequisites above still apply. On Linux, install the host prerequisites separately and keep them on PATH. VM images are created by coop setup.

Nix packages identify as development builds, which disables coop update and background release notifications. To upgrade, update your checkout and run nix profile upgrade coop (or nix build for a local build). Use nix flake update when intentionally updating the pinned Nix inputs.

Removing a Nix installation

coop uninstall refuses Nix-store binaries before changing any data, even with --yes or --purge. Do not use sudo coop uninstall or delete store paths. For a profile installation, remove the package with:

nix profile remove coop

This leaves VM instances, images, configuration and update-check state in place. For a declarative NixOS or Home Manager installation, remove the package from your configuration and rebuild instead.

If you also want to delete coop's data, perform the following cleanup before removing the package, while the coop command is still available. Back up any VM-only work first; this destroys all instances and images for the selected configuration. With the default configuration and data directory:

coop destroy --all && rm -rf -- "$HOME/.coop"

destroy --all stops and destroys the VMs, cleans up backend resources and removes coop's SSH config entries. The second command removes the remaining data and configuration. With a custom configuration, use coop --config /path/to/config.toml destroy --all, then remove the actual data_dir configured there, rather than assuming it is ~/.coop. A config file outside that directory must be removed separately if no longer needed. On Linux, root-owned files may require elevated permissions for that data-directory cleanup; never apply it to the Nix store.

Remove the update-check state separately, if present:

# Linux
rm -f -- "${XDG_STATE_HOME:-$HOME/.local/state}/coop/update-check.json"
# macOS
rm -f -- "$HOME/Library/Application Support/coop/update-check.json"

Then remove the package as described above. Credentials stored separately in a keychain, password manager, or secret-store directory are not removed by these steps.

Configuration

coop reads ~/.coop/config.toml by default. Override the path with --config. If the file doesn't exist, coop falls back to built-in defaults. Run coop init to generate a starter config file.

A minimal config (an empty file is valid; all fields have defaults):

Defaults: 2 vCPUs, 4 GiB RAM, 8 GiB template disk. Override any of them:

[vm]
vcpu_count = 4
mem_size_mib = 8192
template_size_gib = 20

All VM artifacts (kernel, rootfs images, instance disks) live under ~/.coop/.

The guest runs as an unprivileged user (ubuntu, uid 1000, by default) with ~/.local/bin on PATH for every session. Override the username at setup with coop setup --guest-user <name>; see Guest user for details.

Agent integration

Forward your API keys and GitHub credentials into the guest:

github = "auto"

[vm]
vcpu_count = 4
mem_size_mib = 8192

[claude]
config_dir = "~/.claude"

[codex]
auth = "api_key"
config_dir = "~/.codex"

[grok]
config_dir = "~/.grok"

The github field controls how coop resolves a GitHub token for the guest:

  • "off" (default): disables GitHub auth forwarding
  • "auto": checks $GITHUB_TOKEN env var first, falls back to gh auth token if unset
  • "env": requires GITHUB_TOKEN in your environment
  • "pat": forwards a per-repo fine-grained PAT recorded under [github.pat."owner/repo"]. GitHub enforces the token's scope server-side — see GitHub auth for the full reference.

GitHub auth is off by default. Set github = "auto" (or run coop github setup-pat --repo owner/name for a scoped PAT) to enable it. coop up offers to run the PAT wizard inline the first time you bring up a project backed by a GitHub repo without auth configured.

coop picks up ANTHROPIC_API_KEY, XAI_API_KEY, and, in the default Codex API-key mode, OPENAI_API_KEY from your environment automatically. Setting them explicitly under claude.api_key, codex.api_key, or grok.api_key also works, but environment variables are preferred.

For Codex account or workspace access without OpenAI API billing, set [codex] auth = "chatgpt" and rebuild any old image with coop setup --rebuild. An existing VM keeps its own guest disk across a restart, so also run coop restore <vm> --image <image> --reprovision (see Codex integration) to pick up the rebuilt image. Reprovisioning replaces the guest disk, so save guest-only work first. Then run coop codex -- login --device-auth once.

First run

In a hurry? From your project directory, coop quickstart runs setup, brings up an instance, and launches Claude Code in one command — building the default image only if it is missing and reconnecting to an existing instance when one is already running. The steps below walk through the same flow one command at a time and give you per-instance control. See the quickstart reference for details.

1. Setup

coop setup downloads the Firecracker binary and kernel (Linux) or configures Lima (macOS), then builds a template rootfs image. The template ships with base packages (git, curl, build-essential, Docker, and others), the GitHub CLI, Claude Code, and Codex.

coop setup

Install language toolchains into the template with --profile:

coop setup --profile python,node

Built-in profiles: python, node, c, fuzz, rust, go. Combine them with commas (e.g. --profile python,node,rust). Use coop profiles list to inspect what each one installs.

You can also let up build a profile-derived image on demand:

coop up --profile python,node

This creates or refreshes an image named from the sorted profile list (node-python here), then creates the project instance from it.

Skip confirmation prompts with -y:

coop setup -y --profile python

Setup is idempotent. Rerunning with the same profiles skips completed work. Pass --rebuild to force a fresh template build.

2. Bring up a project environment

For normal project work, use coop up from your project directory:

cd ~/code/my-project
coop up

coop up is project-oriented and re-runnable. It creates an instance the first time, reuses it if it is already running, and restarts it after coop stop. By default it copies/syncs the project into /workspace.

Choose mount transport explicitly:

coop up . --mount

On macOS/Lima this is live filesystem sharing. On Linux/Firecracker it is a one-time sync.

Mount additional data directories when creating the project instance:

coop up . --extra-mount ~/data:/data

Or clone a remote repository directly into /workspace inside the guest:

coop up --git-repo https://github.com/trailofbits/coop.git

Tune a project environment at startup (each flag is repeatable where it makes sense, and works on both coop up and coop start):

coop up --forward-port 3000        # tunnel a guest port to the host
coop up --env RUST_LOG=debug       # set a guest env var
coop up --post-start "npm install" # run a command after every boot

See the command reference and configuration reference for the full behavior of --forward-port, --env, and --post-start. After the environment is running, connect to it:

coop shell
coop claude
coop codex
coop grok

3. Restart a stopped instance

coop start

coop start starts existing stopped instances. Use it after coop stop when you want to boot the same VM disk again. If exactly one stopped instance exists, the name is optional.

Restart a specific stopped instance:

coop start my-project

Restart by project path when the instance was created for that project:

coop start --workspace ~/code/my-project

Project creation options belong to coop up, not coop start:

coop up ~/code/my-project --disk 40 --mount
coop up ~/code/my-project --profile python,node
coop up --git-repo https://github.com/trailofbits/coop.git

Skip Claude Code, Codex, and Grok Build credential/config injection:

coop start my-project --no-agents

4. Connect

Launch Claude Code inside the VM:

coop claude

During VM startup, coop writes ~/.claude/settings.json in the guest with defaultMode: bypassPermissions and skipDangerousModePermissionPrompt: true, so Claude Code runs without permission prompts by default. The VM itself is the isolation boundary. For permission prompts (coop launches claude with --permission-mode default):

coop claude --ask

Pass extra arguments through to claude:

coop claude -- --model opus

Launch Codex inside the VM:

coop codex

With [codex] auth = "chatgpt", first sign in from the guest:

coop codex -- login --device-auth

Pass extra arguments through to codex:

coop codex -- --model gpt-5

Launch Grok Build inside the VM:

coop grok

coop launches grok --always-approve --trust --cwd /workspace. For permission prompts, pass --ask (coop passes --permission-mode default). A host ~/.grok/auth.json is copied into the guest on boot. If you have not signed in on the host, use device-code auth (there is no browser in the guest):

coop grok -- login --device-auth

Pass extra arguments through to grok:

coop grok -- --model grok-4.6

Open a shell in the VM:

coop shell

Run a command non-interactively:

coop shell -- ls /workspace
coop exec -- docker ps

5. Check status

coop status

For a specific instance:

coop status my-project

Running instances report resource usage: load average, memory, and disk.

6. Sync files

Push local changes into a running VM:

coop push

Pull guest changes into a new review directory on the host:

coop pull --dir ../my-project-from-vm

Both commands default to the workspace path recorded by coop up, but pull refuses that normally nonempty directory unless --force is supplied. Prefer pulling into a missing or empty directory, reviewing the untrusted guest files, and only then copying back what you want. Override the push source or pull destination with --dir:

coop push --dir ~/other-dir
coop pull --dir ~/new-review-dir

7. Tear down

Stop an instance (preserves disk state):

coop stop my-project

Destroy an instance (deletes its disk and resources):

coop destroy my-project

Remove everything, including all instances, images, kernel, and Firecracker binary:

coop destroy --all

Named images

Build multiple template images with different profiles:

coop setup --image python-dev --profile python
coop setup --image polyglot --profile python,node,rust

Create a project environment from a specific image:

coop up . --image python-dev

List images:

coop images

Delete an image:

coop images --delete python-dev

Other commands

Command Description
coop validate Check config and prerequisites without changing anything
coop logs Stream VM serial console logs (-f to follow)
coop editor Open VS Code or Zed connected to the guest via SSH
coop ssh-config Install a coop-<name> SSH alias for ad-hoc ssh/scp/rsync
coop resize --size +20 Grow a stopped instance's disk by 20 GiB
coop resize --size 100 Set a stopped instance's disk to 100 GiB
coop resize --mem 8192 --vcpus 4 Change a stopped instance's memory and vCPUs

Further reading