I tried it and it worked great at first but I had multiple issues with it, the disk space usage was growing significantly, I need to login multiple times for each sandbox, it's closed source and not possible to customize to my need.
One other thing, I want to be able to handle multiple repos in the same sandbox and have a standard workflow around worktrees (one worktree per repo, all the worktree mounted in the VM).
Local first is also a feature, for work you often can't run company code on a remote VM that isn't company owned. Remote dev envs are on the roadmap though, that's one of the reasons why I designed the declarative clawk.mod manifest
The way I did it, I've got a convention where I store git checkouts in a specific directory on my laptop. I use the repo name as an argument and the wrapper script creates / shells into the per-project VM.
It uses cloud-init for the initial setup and injects the agent's config file and some other stuff in a ro mount from my laptop. That single subdirectory is mounted rw into a conventional location inside the VM. I inject only credentials to e.g. test environments that don't matter if they're exposed. The agent context instructs it that it can use full sudo and install any utilities from apt that it needs.
So for the ux I do incus-dev-vm.sh shell <projname> or incus-dev-vm.sh opencode <projname> etc.
A fun thing about this is I used nested virt to have the agent write the script - so I spooled up an incus VM myself first, then the agent can run with full privs inside there creating nested VMs to ensure its functionality.
Right now I'm not filtering anything on the VM - with my threat model only limited access is granted and I don't give creds to touch real infra. But you should be able to filter it on the host (I haven't researched the best approach however).
It was leveraging apple container in the first versions. They have the same foundation VZ (Virtualization.framework), build one container per VM. The reason I stayed with raw VZ instead of apple container is the network filtering feature
There are two sides to this. The first is security, which plenty of comments already covered. The second, and the real one for me: my tests spin up Docker containers, and I was building a Kubernetes tool (argocd/flux style) that needs a real cluster in the sandbox. In a container that means Docker-in-Docker, which always felt hacky. A VM is just a normal Linux box where Docker and k8s run like they do everywhere else. A separate user can't give you that, it shares your one kernel and whatever's already installed on the host.
Hadn't seen yoloai before. I really like new/diff/apply/destroy workflow, that's interesting. For my own needs the two things I was after were multi-repo worktrees (one sandbox spanning several repos, each on with its own worktree) and a single network restricted VM path I fully control, rather than many backends (I started with many backends at first but it was awkward to add network filtering to them).
Lots of overlap though, nice work, and I'll be reading through yours.
Thanks! There's no packet firewall at all, no iptables/nftables. On macOS the VM's NIC is a Virtualization.framework file-handle device. The daemon runs gvproxy, which terminates the guest's connections and re-dials them as host sockets, so I filter with an allow-list right before the dial.
One caveat, since you asked about root specifically: that's the macOS path, and it only works thanks to the fd NIC. Firecracker on Linux only speaks a TAP, which needs root, so there I do shell out to sudo, but just for the device. The filtering is still the same userspace allow-list.
Thanks for the pointers!! I'm using passt, didn't know about gvproxy. This is awesome and could provide rootless filtering and firewall for my guest VMs. I'm going to experiment with this!
My pleasure! You can find my fork here: https://github.com/clawkwork/gvisor-tap-vsock — the diff is tiny, just a hook in the TCP/UDP/ICMP forwarders that consults an allow-list before dialing
One other thing, I want to be able to handle multiple repos in the same sandbox and have a standard workflow around worktrees (one worktree per repo, all the worktree mounted in the VM).
These were some of the reasons that led me to build: Clawk - https://github.com/clawkwork/clawk