
Three VMs from one prompt: the foundation Proxmox topology — PVE host + LXC + VM
June 5, 2026
Proxmox VE is the #1 homelab host OS — 42.2% of respondents in the 2026 selfh.st survey, ahead of bare Debian or Ubuntu. For most people the starting shape is the same: one mini-PC, Proxmox on the metal, a handful of LXC containers for utility services, and one or two VMs for heavier apps.
This post walks through that exact shape as an OpenFactory build prompt: three buildable Debian Trixie VMs — a Proxmox host node, one LXC container (Pi-hole), and one VM workload — from a single prompt, with the /etc/pve/ config tree, sample CT100 / VM200 conf files, and a mock PVE API on :8006 already baked in. Real pve-manager is a source-ISO install at deploy time; the lab gives you the topology and the configs to drop on top of it.
pve-node (10.80.0.10:8006) — the PVE host with /etc/pve/{nodes,storage,qemu-server,lxc,priv}/ already laid out and a mock PVE API responding on :8006/api2/json/version / /nodes / /cluster/status.lxc-pihole (10.80.0.20) — container template at /etc/pve/lxc/100.conf with hostname, rootfs, network, and unprivileged flag set; mock Pi-hole admin on :80 + DNS socket on :53.vm-app (10.80.0.30:8080) — VM template at /etc/pve/qemu-server/200.conf with virtio NIC, SCSI disk, and agent: 1; mock health + info endpoints./etc/pve/storage.cfg with dir: local and lvmthin: local-lvm pre-populated.pve-manager on top./etc/pve/lxc/100.conf isn't shaped right, if the mock API returns the wrong node id, or if the host can't reach the children on the lab subnet.Three Debian Trixie VMs on 10.80.0.0/24. The PVE host manages the two children (CT100 and VMID 200) through the mock PVE API on :8006; the children expose their own service ports on the lab subnet.
Paste this verbatim into the chat builder at console.openfactory.tech. Nothing above or below it — the builder expects the prompt body to start at the “Build a compact multi-node lab…” line.
Build a compact multi-node lab named `proxmox-single-node-lab`.
Output discipline: keep the plan small. Use one startup script per node, about 25 shell lines or less. Do not install `pve-manager`, `qemu-kvm`, `lxc`, the Proxmox apt repos, or any kernel modules at build time. The PVE host is a source-ISO deploy; this lab only stands up the configuration shape and a tiny Python stdlib HTTP service that mimics the PVE API. Write deployment-time config examples and tiny Python stdlib or shell compatibility stubs only. The goal is a buildable preparation lab, not a production Proxmox install.
## Topology
Create 3 buildable `debian-trixie` nodes, all `x86_64`, SSH enabled, DHCP/default route intact with lab aliases, firewall disabled, DNS `1.1.1.1` and `8.8.8.8`, user `ops` password `pve-ops` in `sudo`. Every recipe must set top-level `test_config` to `{ "enabled": false, "tests": [] }`.
- `pve-node`: role `pve-host`, 4 GB RAM, 32 GB disk, alias `10.80.0.10/24`, x `230`, y `60`
- `lxc-pihole`: role `lxc-container`, 1 GB RAM, 8 GB disk, alias `10.80.0.20/24`, x `110`, y `220`
- `vm-app`: role `vm-workload`, 2 GB RAM, 16 GB disk, alias `10.80.0.30/24`, x `350`, y `220`
Connections: `pve-node` to both children on `:22`; `pve-node` manages `lxc-pihole` (CT 100) and `vm-app` (VMID 200) via mock PVE API on `:8006`.
## Common Recipe Requirements
All nodes: features `headless`, `ssh`; packages `openssh-server`, `python3`, `curl`, `jq`, `iproute2`, `netcat-openbsd`, `ca-certificates`. Each startup script adds the alias with `IFACE=$(ip route show default | awk '{print $5; exit}')`, `ip link set "$IFACE" up || true`, and `ip addr add <alias> dev "$IFACE" || true`. If `os.startup_scripts[].after` is present, it must be the string `"network-online.target"`, not an array. Do not install `pve-manager`, `proxmox-backup-server`, `ceph`, `truenas-scale`, or any related apt packages — they are source-ISO deploys handled at provisioning time, not at build time.
## Node Requirements
`pve-node`: features `headless`, `ssh`. Create the deployment-time config tree `/etc/pve/{nodes/pve-node,storage,qemu-server,lxc,priv}` mode `0750 ops:ops` and seed:
- `/etc/pve/storage.cfg` with `dir: local\n path /var/lib/vz\n content backup,iso,vztmpl` plus `lvmthin: local-lvm\n thinpool data\n vgname pve\n content rootdir,images`.
- `/etc/pve/lxc/100.conf` representing the Pi-hole CT: `hostname: pihole`, `ostype: debian`, `arch: amd64`, `memory: 1024`, `swap: 512`, `cores: 1`, `net0: name=eth0,bridge=vmbr0,ip=10.80.0.20/24,gw=10.80.0.1`, `rootfs: local-lvm:vm-100-disk-0,size=8G`, `unprivileged: 1`.
- `/etc/pve/qemu-server/200.conf` representing the VM: `name: vm-app`, `memory: 2048`, `cores: 2`, `ostype: l26`, `agent: 1`, `net0: virtio,bridge=vmbr0`, `scsi0: local-lvm:vm-200-disk-0,size=16G`, `boot: order=scsi0`.
Add a Python stdlib HTTP service on `0.0.0.0:8006` exposing:
- `GET /api2/json/version` -> `200 {"data":{"version":"compat-1.0","release":"pve-compat","repoid":"compat-stub"}}`
- `GET /api2/json/nodes` -> `200 {"data":[{"node":"pve-node","status":"online","cpu":0.05,"maxcpu":4,"mem":1073741824,"maxmem":4294967296}]}`
- `GET /api2/json/cluster/status` -> `200 {"data":[{"type":"node","name":"pve-node","online":1,"local":1,"id":"node/pve-node"}]}`
- `GET /metrics` -> `pve_compat_up 1` plus `pve_node_online{node="pve-node"} 1`
Register `pve-compat.service`. Write `/root/pve-runbook.md` warning that real PVE installs replace this stub by booting the Proxmox VE source ISO and joining storage / networking through `pveceph` / `pvecm`.
`lxc-pihole`: features `headless`, `ssh`. Add a Python stdlib HTTP service on `0.0.0.0:80` exposing `GET /admin/api.php?summary` -> `200 {"status":"enabled","domains_being_blocked":"compat","dns_queries_today":"0"}` and `GET /metrics` with `pihole_compat_up 1`. Bind a Python stdlib TCP listener on `0.0.0.0:53` accepting connections (no DNS protocol needed). Register `pihole-compat.service`.
`vm-app`: features `headless`, `ssh`. Add a Python stdlib HTTP service on `0.0.0.0:8080` exposing `GET /health` -> `200 {"status":"ok","node":"vm-app","vmid":200}`, `GET /api/info` -> `200 {"name":"vm-app","vmid":200,"pve_host":"pve-node"}`, `GET /metrics` with `vm_app_compat_up 1`. Register `vm-app-compat.service`.
## Scenario
Emit exactly one group scenario named `proxmox-single-node-lab-validation`. Put `custom_tests[].assertions[]` inside the scenario entry; leave `scenarios[].tests` empty. Every assertion needs `on_vm`. Use only `port_listening`, `command_output`, and `http_responds`; do not emit `vm_boots`, `network_reachable`, or `service_running`.
- `Stack ports listen`: `port_listening` for `pve-node:8006`, `lxc-pihole:80`, `lxc-pihole:53`, `vm-app:8080`.
- `PVE API version`: on `pve-node`, `curl -fsS http://localhost:8006/api2/json/version | jq -e '.data.version == "compat-1.0"' >/dev/null && echo pve-ok`.
- `PVE node listing`: on `pve-node`, `curl -fsS http://localhost:8006/api2/json/nodes | jq -e '.data[0].node == "pve-node"' >/dev/null && echo node-listed`.
- `LXC + VM config files present`: on `pve-node`, `test -s /etc/pve/lxc/100.conf && test -s /etc/pve/qemu-server/200.conf && grep -q 'hostname: pihole' /etc/pve/lxc/100.conf && grep -q 'name: vm-app' /etc/pve/qemu-server/200.conf && echo configs-present`.
- `Child workloads respond`: on `lxc-pihole`, `curl -fsS 'http://localhost/admin/api.php?summary' | jq -e '.status == "enabled"' >/dev/null && echo lxc-ok`; on `vm-app`, `curl -fsS http://localhost:8080/health | jq -e '.vmid == 200' >/dev/null && echo vm-ok`.
- `Host reaches both children`: on `pve-node`, `nc -z -w 5 10.80.0.20 80 && nc -z -w 5 10.80.0.30 8080 && echo children-reachable`.
Preserve warnings that real Proxmox VE installation from the source ISO, `pvecm` cluster init / join, LVM-thin or ZFS root, real bridge / VLAN setup on `vmbr0`, LXC template downloads via `pveam`, VM disk image upload via `qm importdisk`, real Pi-hole installation in the LXC, snapshot scheduling, off-host backups via PBS, and `10.80.0.0/24` lab aliasing are deployment-time concerns.Driving OpenFactory from an AI agent instead of the browser? The same flow is exposed through the OpenFactory MCP server — submit the prompt programmatically, get the build-plan preview back, and call create_build / start_vm on the resulting recipes. Single-image builds go straight through the openfactory CLI.
The prompt produces a buildable preparation lab — the right topology, the right ports listening, deployment-time config templates dropped in the right places, and tiny compatibility services that prove the wiring works. A few things still sit outside the recipe and need operator attention before this carries real load:
/etc/pve after install.local-lvm; real install picks LVM-thin or ZFS during the PVE installer.vmbr0 on the host plus any VLAN tags your network needs. The lab assumes a flat bridge.pveam download local debian-12-standard... for the CT, qm importdisk for the VM. The config files are already in the right shape; the disks are yours to seed.rsync nightly to another host or graduate to the PBS backup pattern in the post below.The natural next step is the 3-node Proxmox cluster when one host stops being enough — same prompt vocabulary, three PVE nodes plus a QDevice witness. For the DNS layer the LXC anchors, see the Pi-hole DNS cluster post. And the Enterprise & GxP page covers fleet rollouts.
OpenFactory's free flow is for browsing. Persistent VMs, SSH access, snapshots, your own ISO, and fleet deployment live on a paid plan.