Install
openclaw skills install vagrant-skillDisposable VMs for safe testing — full sudo, Docker, Go, nested KVM, destroy and recreate. Use when you need a sandbox to build, test, or break things without affecting the host. Use when the user says "spin up a VM", "test in isolation", "run with sudo", "try something dangerous", "set up a dev environment", "I need Docker", "test firewall rules", "clean environment", or needs root access, network testing, or safe experimentation.
openclaw skills install vagrant-skill$ARGUMENTS
You MUST consider the user input before proceeding (if not empty).
This skill provides a disposable VM sandbox for safe development and testing. All privileged operations (sudo, iptables, Docker, systemd, KVM) happen inside the VM — the host is never modified.
You (the agent) should NEVER need host-level sudo. Instead, use this Vagrant workflow to get a fully provisioned, disposable VM where you have unrestricted root access.
Before using this workflow, verify a provider is available:
# Check Vagrant is installed
command -v vagrant
# Check for a provider (any one is sufficient)
command -v prlctl # Parallels (Mac Apple Silicon — recommended)
vagrant plugin list | grep libvirt # libvirt (Linux — nested KVM)
command -v VBoxManage # VirtualBox (fallback)
See references/platform-setup.md for detailed provider installation.
If the user's project does not already have a Vagrantfile, you MUST create a real, working one in their project directory. This is not a template or example — it must work end-to-end with vagrant up.
Requirements for the Vagrantfile you create:
bento/ubuntu-24.04config.vm.synced_folder ".", "/project", type: "rsync"go.mod, package.json, requirements.txt, Makefile, Dockerfile, etc.)set -euo pipefail in provisioning scriptsHere is the base Vagrantfile — you MUST customize the provisioning section based on what the project actually uses:
# -*- mode: ruby -*-
# Vagrantfile — disposable dev/test VM
VM_CPUS = Integer(ENV["VM_CPUS"] || 4)
VM_MEMORY = Integer(ENV["VM_MEMORY"] || 4096)
Vagrant.configure("2") do |config|
config.vm.box = "bento/ubuntu-24.04"
config.vm.box_check_update = false
config.vm.hostname = "dev"
config.vm.boot_timeout = 300
config.ssh.forward_agent = true
# ─── Sync project into VM at /project ─────────────────────────────────────
config.vm.synced_folder ".", "/project", type: "rsync",
rsync__exclude: [
".git/", "node_modules/", "vendor/", ".vagrant/",
"bin/", "dist/", "build/", ".next/",
]
# ─── Provider: Parallels (Mac Apple Silicon — recommended) ────────────────
config.vm.provider "parallels" do |prl|
prl.cpus = VM_CPUS
prl.memory = VM_MEMORY
prl.update_guest_tools = true
end
# ─── Provider: libvirt (Linux — preferred, nested KVM) ────────────────────
config.vm.provider "libvirt" do |lv|
lv.cpus = VM_CPUS
lv.memory = VM_MEMORY
lv.cpu_mode = "host-passthrough"
lv.nested = true
end
# ─── Provider: VirtualBox (cross-platform fallback) ───────────────────────
config.vm.provider "virtualbox" do |vb|
vb.cpus = VM_CPUS
vb.memory = VM_MEMORY
vb.customize ["modifyvm", :id, "--nested-hw-virt", "on"]
end
# ─── Provision: install project dependencies ──────────────────────────────
# CUSTOMIZE THIS for the project. Inspect go.mod, package.json,
# requirements.txt, Dockerfile, Makefile, etc. and install what's needed.
config.vm.provision "shell", privileged: true, inline: <<-SHELL
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq build-essential curl git jq ca-certificates gnupg
# ── Docker ────────────────────────────────────────────────────────────
if ! command -v docker &>/dev/null; then
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io
fi
systemctl enable --now docker
usermod -aG docker vagrant
# ── Add project-specific tooling below ────────────────────────────────
# Examples (uncomment / add what the project needs):
#
# Go:
# GO_VERSION="1.24.3"
# curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-$(dpkg --print-architecture).tar.gz" \
# | tar -C /usr/local -xz
# echo 'export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH"' > /etc/profile.d/go.sh
#
# Node.js:
# curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
# apt-get install -y -qq nodejs
#
# Python:
# apt-get install -y -qq python3 python3-pip python3-venv
echo "VM ready — project synced at /project"
SHELL
end
You MUST customize the provisioning section. Read the project to determine what it needs:
go.mod → install Gopackage.json → install Node.jsrequirements.txt / pyproject.toml → install PythonDockerfile → Docker is already included aboveiptables dnsmasq dnsutils iproute2 net-toolsqemu-kvm libvirt-daemon-systemUncomment the relevant blocks and add any other tooling. Do NOT leave placeholder comments in the final Vagrantfile — produce a clean, working file.
Then add .vagrant/ to the user's .gitignore if not already present:
grep -qxF '.vagrant/' .gitignore 2>/dev/null || echo '.vagrant/' >> .gitignore
The Vagrantfile itself should be committed — it's reusable project infrastructure.
If the user already has a Vagrantfile, use it as-is unless they ask to modify it.
vagrant up
This boots the VM with the user's project synced at /project inside the VM.
All commands use vagrant ssh -c from the host. No interactive SSH needed.
# Run any command with sudo
vagrant ssh -c "sudo apt-get install -y some-package"
# Build the project
vagrant ssh -c "cd /project && make build"
vagrant ssh -c "cd /project && go test ./..."
# Docker operations
vagrant ssh -c "docker build -t myimage ."
vagrant ssh -c "docker run --rm myimage"
# Network/firewall testing
vagrant ssh -c "sudo iptables -L -n"
When you modify source on the host:
vagrant rsync # sync changes to VM
vagrant ssh -c "cd /project && make build" # rebuild
vagrant ssh -c "cd /project && make test" # test
vagrant destroy -f # destroys VM completely, clean slate
vagrant rsync && vagrant ssh -c "cd /project && make build"
vagrant ssh -c "cd /project && make test"
# If tests fail, fix code on host, repeat
vagrant ssh -c "cd /project && docker build -t test ."
vagrant ssh -c "docker run --rm test"
vagrant ssh -c "sudo iptables -A FORWARD -s 172.16.0.0/24 -j DROP"
vagrant ssh -c "sudo iptables -L -n -v"
Run a bats-core test suite against a live VM as proof that the system under test works. The VM must be up before running bats.
vagrant up
bats test/e2e.bats # run tests, output is the proof
vagrant destroy -f # tear down after
Capture output to show the user:
vagrant up
bats test/e2e.bats 2>&1 | tee /tmp/bats-results.txt
vagrant destroy -f
cat /tmp/bats-results.txt
bats exits non-zero on any failure — treat that as a test run failure.
vagrant destroy -f && vagrant up
Environment variables to customize the VM (set before vagrant up):
| Variable | Default | Purpose |
|---|---|---|
VM_CPUS | 4 | Number of vCPUs |
VM_MEMORY | 4096 | RAM in MB |
Example:
VM_CPUS=8 VM_MEMORY=8192 vagrant up
See references/vm-contents.md for full details on VM filesystem layout and installed software.
vagrant destroy -f removes everythingvagrant provision is safe to re-run.vagrant/ is gitignoredUser says: "I need to test some firewall rules before deploying to production"
Actions:
iptables dnsmasq dnsutils iproute2 net-tools provisionedvagrant upvagrant ssh -c "sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT"vagrant ssh -c "sudo iptables -A INPUT -p tcp --dport 0:442 -j DROP"vagrant ssh -c "sudo iptables -L -n -v" — verify rules look rightvagrant ssh -c "sudo iptables-save > /project/firewall.rules" — export if goodvagrant rsync to get rules file back, or vagrant destroy -f to scrapResult: Firewall rules iterated safely. Host network never touched. Rules exportable.
User says: "I need to test this systemd unit file before deploying"
Actions:
vagrant upvagrant ssh -c "sudo cp /project/myservice.service /etc/systemd/system/"vagrant ssh -c "sudo systemctl daemon-reload && sudo systemctl start myservice"vagrant ssh -c "systemctl status myservice" — check it worksvagrant ssh -c "sudo journalctl -u myservice --no-pager" — check logsvagrant destroy -f — clean slateResult: Service tested with real systemd, real journald. No risk to host init system.
User says: "I need to test a Docker compose setup that binds port 80"
Actions:
vagrant upvagrant ssh -c "cd /project && docker compose up -d"vagrant ssh -c "curl -sf http://localhost" — test from inside VMvagrant ssh -c "docker compose logs" — check outputvagrant ssh -c "docker compose down" — cleanupvagrant destroy -fResult: Full Docker compose stack running with privileged ports — impossible without sudo on the host.
Three working examples live under examples/ in this skill's directory. All follow the same pattern — boot, test, tear down:
cd examples/<name>
vagrant up [--provider=<provider>]
bats test/e2e.bats 2>&1 | tee /tmp/e2e-results.txt
cat /tmp/e2e-results.txt
vagrant destroy -f
examples/nginx-hardened/ — Linux / libvirt (16 tests)Deploys nginx + hardened iptables (INPUT DROP, allow SSH + HTTP only).
Why VM: iptables -F INPUT; iptables -P INPUT DROP on the host locks you out.
cd examples/nginx-hardened
vagrant up
bats test/e2e.bats 2>&1 | tee /tmp/e2e-results.txt
vagrant destroy -f
examples/mac-docker-compose/ — Mac Apple Silicon / Parallels (14 tests)Runs a Docker Compose stack: nginx on port 80 proxying a Python JSON API. Why VM: Docker Desktop requires a commercial license; Docker CE in a VM has none of its restrictions.
cd examples/mac-docker-compose
vagrant up --provider=parallels
bats test/e2e.bats 2>&1 | tee /tmp/e2e-results.txt
vagrant destroy -f
examples/windows-systemd-service/ — Windows WSL2 / VirtualBox (20 tests)Deploys a Python HTTP server as a real systemd unit, running as a dedicated system user. Why VM: WSL2 does not run real systemd — unit files cannot be tested without a real Linux init.
WSL2 pre-flight:
export VAGRANT_WSL_ENABLE_WINDOWS_ACCESS="1"
export PATH="$PATH:/mnt/c/Program Files/Oracle/VirtualBox"
cd examples/windows-systemd-service
vagrant up --provider=virtualbox
bats test/e2e.bats 2>&1 | tee /tmp/e2e-results.txt
vagrant destroy -f
Error: vagrant up hangs or times out
Cause: Provider not installed or configured correctly
Solution:
vagrant plugin listvagrant up --debug 2>&1 | tail -50vagrant up --provider=virtualboxError: /project directory is empty or missing inside VM
Cause: rsync failed or synced_folder misconfigured
Solution:
vagrant rsyncsynced_folder ".", "/project", type: "rsync"Error: vagrant up uses wrong provider
Cause: Multiple providers installed, Vagrant auto-selects
Solution:
vagrant statusvagrant up --provider=parallelsError: /dev/kvm missing inside the VM
Cause: Host doesn't support nested virtualization or provider not configured
Solution:
test -e /dev/kvm on hostcpu_mode = "host-passthrough"