Automates the Attach Disk & Move Disk (minimal downtime) migration method from the Proxmox wiki. Optionally uses NetApp Shift Toolkit for disk conversion, which can reduce migration time by up to 90% on large VMs.
Both the base method and NetApp Shift-based version has been used to migrate over 150+ VMs in sizes ranging from 20GB to 5TB.
The script is split up into different steps that differs some depending on configuration.
Base method:
- Storage vMotion VM to a shared datastore
- VM creation in Proxmox
- NIC configuration export
- Enablement of VirtIO SCSI boot driver
- Installation of VirtIO guest tools
- Shutdown of the VM
- Rewrite VMDK descriptors on the Proxmox node to prepare for disk conversion
- Start VM in Proxmox
- Move disks to final storage (this does the VMDK to qcow2)
- Import converted disks
- Verify VM is running on final storage
- Install VirtIO drivers from ISO via QEMU guest agent
- Purge VMware Tools
- Restore NIC configuration
- Enable NICs and do a final reboot
NetApp Shift method (replaces steps 7–10 in base method):
- Storage vMotion VM to a shared datastore
- VM creation in Proxmox
- NIC configuration export
- Enablement of VirtIO SCSI boot driver
- Installation of VirtIO guest tools
- Shutdown of the VM
- Create Resource group for the VM in NetApp Shift
- Create Blueprint referencing the Resource group
- Trigger a disk conversion using the Blueprint and wait for it to finish
- Move converted QCOW2 disks into correct VM directory, if VM uses OVMF create a efidisk and boot up the VM.
- Verify VM is running on final storage
- Install VirtIO drivers from ISO via QEMU guest agent
- Purge VMware Tools
- Restore NIC configuration
- Enable NICs and do a final reboot
- Python 3.10+ on the host running the migration
- VMware Tools running on the guest to be migrated
- VirtIO drivers and guest tools staged in the guest (
C:\TMP\pveMigration\) - PowerShell scripts (
exportNicConfig.ps1,enable-vioscsi-to-load-on-boot.ps1,importNicConfig.ps1,purge-vmware-tools.ps1) in the same directory - VirtIO ISO uploaded to Proxmox storage
- SSH access to the Proxmox node (password auth reuses the API password; when using an API token, set
proxmox.ssh_key_fileor use an SSH agent) - The same NFS volume mounted on both Proxmox and in vCenter (can be a temporary migration volume)
- A "final" destination volume in Proxmox (Should not be the same as the volume above)
- A server running NetApp Shift Toolkit that has access to the PVE API, vCenter and ONTAP API
- vCenter setup as a source site inside Shift
- All ONTAP arrays that will be used added as storage to vCenter inside Shift
- KVM (Conversion) setup as a source destination inside Shift
- NFS volume mounted on Proxmox
- The same NFS volume mounted inside vCenter(This must be the same as above)
python -m venv .venv
source .venv/bin/activate
pip install .
cp config.example.yaml config.yamlEdit config.yaml with your credentials and VM info.
(pip install -r requirements.txt still works if you prefer running from the source tree without installing.)
vmware-to-proxmox --config config.yaml
# or, from the source tree:
python migrate.py --config config.yamlAll settings in config.yaml can be overridden with CLI flags. Run python migrate.py --help for the full list.
| Flag | Purpose |
|---|---|
--dry-run |
Log actions without making changes |
--skip-to N |
Resume from step N (1-15) |
--parallel |
Migrate all VMs concurrently |
--max-parallel N |
Cap concurrent migrations (default: 3) |
--skip-preflight |
Skip the pre-flight validation checks |
--verbose |
Debug-level logging |
Before step 1, the tool validates everything the run will need: the vCenter migration datastore, the Proxmox storages, the NIC bridges, an explicit VMID being free, the VirtIO ISO (Windows guests), and SSH connectivity to the Proxmox node. All failing checks are reported at once, so nothing dies halfway through a multi-hour migration. Bypass with --skip-preflight if your API user lacks permissions for some of the read-only queries.
When a migration does fail, the error includes the failing step number and a ready-to-run --skip-to N resume command.
By default, NICs are created with link_down=1 and enabled in step 15. This prevents odd things from happening during the migration.
Set enable_nics_on_boot: true (or --enable-nics-on-boot) to create NICs with link enabled from the start. This halves all boot wait timers in steps 8–15, significantly reducing total migration time for domain-joined servers that would otherwise wait for domain controller timeouts.
Step 3 exports each NIC's static IPv4 address/subnet/gateway and DNS servers, plus any stable IPv6 addresses and default gateway (link-local and temporary/privacy IPv6 addresses are excluded — they regenerate automatically). Step 14 restores this configuration onto the new VirtIO NICs by position. IPv6 restoration is best-effort: SLAAC/DHCPv6 may already have assigned an equivalent address by the time step 14 runs, so a conflicting "address already exists" is logged as a warning rather than failing the migration.
By default Proxmox generates new MAC addresses for all NICs. Set preserve_mac: true (or --preserve-mac) to reuse the original VMware MAC addresses — useful when DHCP reservations, license bindings, or MAC-based firewall rules depend on them. Can be overridden per VM in the vms: list.
Add a vms: list under migration: in your config. Shared settings go at the top; any field can be overridden per VM.
migration:
migration_datastore: "shared-ds"
proxmox_storage: "migration-nfs"
proxmox_final_storage: "local-lvm"
proxmox_bridges: "vmbr0"
enable_nics_on_boot: false
vms:
- vm_name: "web-server"
proxmox_vmid: 200
- vm_name: "db-server"
proxmox_vmid: 201
proxmox_bridges: "vmbr1"
proxmox_final_storage: "ceph-pool"
cpu_type: "x86-64-v2-AES"
max_cores: 4
enable_nics_on_boot: true
enable_ha: trueSequential by default. Use --parallel for concurrent migration. Depending on what storage is being used remember that a VM migration causes heavy load on the target storage. Concurrency is capped at 3 by default (--max-parallel / max_parallel to change); I recommend max 2-4 VMs in parallel, but prefer to run them in sequence.
VMIDs are safe in parallel runs: auto-allocated VMIDs are reserved per VM so concurrent migrations never collide, and duplicate vm_name/proxmox_vmid entries in the config are rejected at startup.
- See Issues
enable-vioscsi-to-load-on-boot.ps1— croit/derhannsimportNicConfig.ps1&exportNicConfig.ps1— lucavornhederpurge-vmware-tools.ps1— community gist- NetApp for their Shift Toolkit and examples at shift-api-automation
This project was built with assistance from AI. AI makes mistakes — and so do humans. Always have working backups before migrating and verify they work. Do a trial run with --dry-run and --skip-to before running on production workloads.
YMMV
