Skip to content

Disposable sandboxes

In Your first virtual machine we spent twenty minutes installing an operating system. In this tutorial we get a working VM from that same image in a few seconds, run a command in it, deliberately make a mess inside it, and throw it away. Then we do it again and watch the mess be gone.

This is the loop worth internalising: build a base image once, then treat individual VMs as cheap and disposable.

The base image from the first tutorial, ~/vms/alpine-agent.qcow2, with qemu-guest-agent installed inside it, and the VM built on it shut down.

  1. Ask the agent for a sandbox:

    sandbox_vm(base_image="~/vms/alpine-agent.qcow2")

    One call does four things: it creates a copy-on-write overlay disk, launches a VM on that overlay, forwards a free host port to guest port 22, and waits for the guest agent to answer before returning.

  2. Read the result closely, because it tells us what we got:

    {
    "name": "sandbox",
    "status": "running",
    "sandbox": true,
    "overlay": "~/.local/share/mcqemu/vms/sandbox/overlay.qcow2",
    "base_image": "/home/you/vms/alpine-agent.qcow2",
    "network": "outbound blocked",
    "guest_agent": "responding",
    "port_forwards": ["39221:22"]
    }

    We did not pass a name, so it picked sandbox (the next one would be sandbox-2). guest_agent: responding means guest_exec will work right now, with no waiting and no login.

  3. Prove the guest is real:

    guest_exec(name="sandbox", command="uname", args=["-a"])

The sandbox writes to an overlay file, and the overlay refers back to the base for anything it has not changed. Let us see it.

  1. Write a file inside the guest:

    guest_file_write(name="sandbox", path="/root/evidence.txt",
    content="this sandbox was here\n")
  2. Read it back to confirm it landed:

    guest_file_read(name="sandbox", path="/root/evidence.txt")
  3. Now do some real damage, the kind you would never risk on a machine you cared about:

    guest_exec(name="sandbox", command="/bin/sh", args=["-c", "rm -rf /etc/apk"])
  4. Look at the relationship between the two disks:

    image_info(path="~/.local/share/mcqemu/vms/sandbox/overlay.qcow2", backing_chain=true)

    The chain shows the overlay on top and ~/vms/alpine-agent.qcow2 below it as the backing file. Check the base image’s modification time on the host: it has not changed. Every write the guest made went into the overlay.

Try to reach the internet from inside the sandbox:

guest_exec(name="sandbox", command="/bin/sh", args=["-c", "apk update"])

It fails. That is the point of the default: traffic the guest starts is dropped, so it cannot reach the internet or any service listening on your host. Inbound port forwards still work, which is why the forwarded SSH port is useful even with networking restricted.

When a sandbox legitimately needs to fetch packages, ask for it explicitly with allow_network=true, and know that you have opened the door on purpose.

  1. Destroy the sandbox:

    sandbox_destroy(name="sandbox")

    The result confirms overlay_deleted: true and names the base image it left alone. The VM is force-stopped, removed from the registry, and its overlay and logs are deleted.

  2. Create a fresh one from the same base:

    sandbox_vm(base_image="~/vms/alpine-agent.qcow2")
  3. Look for the damage:

    guest_file_read(name="sandbox", path="/root/evidence.txt")

    The file does not exist, and /etc/apk is back. The new sandbox started from the base image exactly as it was.

  4. Clean up:

    sandbox_destroy(name="sandbox")

A base image plus an overlay gives us VMs that cost seconds instead of minutes, because the expensive part (installing an operating system) already happened and is shared by every sandbox built on it.

Overlays make throwing a VM away the normal outcome rather than a loss. sandbox_destroy then sandbox_vm is a full reset back to a known state, and the base image is safe from anything the guest does because the guest can only write to the overlay.

Restricting outbound traffic by default is what makes it a sandbox rather than just a fast VM. Software running inside it cannot phone anywhere or poke at services on your host unless you decide otherwise.

  • Snapshots if you want checkpoints inside a longer-lived VM instead of a clean slate every time.
  • Install the guest agent to turn another operating system into a base image, including injecting the agent into a downloaded cloud image offline.
  • Sandboxing for what these isolation boundaries do and do not promise.