Your first ZTD VM
By the end of this tutorial you will have booted an isolated Debian VM with its
own Docker daemon, opened a shell inside it, reached a service running in it from
your host, and destroyed it cleanly — all driven through the single ./ztd
interface. Allow about 15 minutes the first time (provider downloads and the base
image are cached afterward).
Before you start
You need a Linux host with:
- Docker installed and running.
- Hardware virtualization (
/dev/kvmpresent).
You do not need Terraform, Task, or any language runtime on the host — those live in the toolbox container.
Step 1 — Install the hypervisor
The local backend boots the VM with the host’s KVM, so libvirt/qemu must be present. On Arch:
sudo pacman -S --needed qemu-full libvirt dnsmasq
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt "$USER"Log out and back in so your shell picks up the libvirt group, then confirm:
systemctl is-active libvirtd # active
ls /dev/kvm # existsStep 2 — Initialize ZTD
cd ztd
./ztd initThe first run builds the toolbox image (Terraform + Task + libvirt client
libraries) and downloads the Terraform providers into .terraform/. You only do
this once.
Step 3 — Boot the VM
./ztd upZTD fetches the Debian 12 cloud image, creates a copy-on-write disk, renders a cloud-init that installs Docker, Node, and the agent runner, and boots the domain. When it finishes it prints the VM’s IP.
Behind the scenes this is Terraform applying the kvm module; ZTD just wraps it
so you don’t think about it.
Step 4 — Look inside
./ztd sshYou’re now in the guest. Verify it has its own Docker, separate from your host’s:
docker info | grep -i 'name\|root dir'Start something that listens, for example:
docker run -d --rm -p 8080:80 nginxLeave it running and exit the shell (exit).
Step 5 — Reach it from the host
./ztd tunnel -- 8080Open http://localhost:8080 — you’re hitting nginx inside the VM through an
SSH tunnel. Stop the tunnel with Ctrl-C when done.
Step 6 — Destroy it
./ztd downThe VM and its disk are gone. Because ZTD treats VMs as cattle, this is the expected end state — anything worth keeping would have been pushed to git first.
What you learned
- The host only ever needed Docker plus a hypervisor; all tooling was containerized.
- The guest is fully isolated, with its own Docker daemon.
- The entire lifecycle is one verb at a time:
init,up,ssh,tunnel,down.
Where to go next
- Use the Proxmox backend to run the same VM on a remote node with nothing but Docker on your machine.
- The zero-trust model explains what “isolated” actually guarantees.