Leaving VMware After Broadcom: A Checked Migration Guide

Leaving VMware After Broadcom: A Checked Migration Guide

Leaving VMware After Broadcom: A Checked Migration Guide

Short answer (checked 2026-11-03): Since buying VMware, Broadcom has stopped selling perpetual licences, folded the product list into two subscription bundles, closed its old cloud-partner programme in Europe and, this autumn, pulled the public download of VDDK, the disk library most migration tools use to read VMware disks. Today The Register reports an open-source, read-only replacement for that library. Not every way out needs VDDK, though: an OVA export plus a disk conversion uses no VMware code at all. Below: the dated changes, realistic targets for an EU team, the move step by step, and a converter we ran on a real VMware-format export.

Source. "Architect creates open-source replacement for VM migration tool VMware has hidden" (Simon Sharwood, The Register, 2026-11-03). Other facts are linked where they appear. Your own contract can differ from the public terms, so confirm licence details with your reseller.

What Broadcom changed, with dates

Date Change Source
2023-12-11 End of sale of perpetual licences and support renewals; offer cut to VMware Cloud Foundation (VCF) and vSphere Foundation (VVF) subscriptions Broadcom news, Krish Prasad
2025-03-28 Distributor Arrow tells partners the minimum order rises from 16 to 72 cores per product line from 2025-04-10 The Register
2026-01-26 Cloud-provider (VCSP) contracts in the EEA not renewed; open deals to close by 2026-03-31; invite-only programme after that The Register, 2026-01-31
Aug-Sep 2026 VDDK removed from public download: "The VDDK is not a customer entitlement", access via select TAP partners TechTarget, 2026-09-09
2026-11-03 OpenVDDK by Andrija Panić: Apache-licensed, read-only drop-in for libvixDiskLib.so.8.0.3 that "contains no VMware code" The Register, 2026-11-03

Two caveats. The 72-core minimum was reported by a distributor, not on a Broadcom page we could find: Broadcom's core-counting article (KB 313548, checked 2026-11-03) says "You must license a minimum of 16 physical cores for each CPU", and some resellers say the 72-core order minimum was dropped again. Ask for the core count on your quote in writing. And the EU side is still open: the cloud trade group CISPE asked the EU General Court in July 2025 to annul the Commission's approval of the merger (case T-503/25); we found no ruling yet.

Why VDDK matters, and which paths avoid it

VDDK's library, libvixDiskLib, lets a tool read the disks of VMs that still live in vSphere. The Register lists virt-v2v, nbdkit, the Forklift / MTV importers, Apache CloudStack and in-house scripts among the tools that load it. Microsoft's VM Conversion extension for Windows Admin Center (vCenter to Hyper-V, with warm sync over Change Block Tracking) is still marked preview in its documentation (updated 2026-02-20), and launch coverage lists VDDK among its prerequisites. OpenVDDK is the new option for all of these, with its author's own warning that tester reports are "not independently audited customer endorsements".

Paths with no VDDK at all, per the virt-v2v VMware input docs (checked 2026-11-03): an OVA export (-i ova), the VM's files on NFS or over SSH (-i vmx, -it ssh), NFC, and the plain vCenter HTTPS path, which the docs call "the slowest method". Proxmox VE's ESXi import wizard, announced on 2024-03-27, reads disks through the ESXi API with its own Rust user-space filesystem and was tested against ESXi 6.5 to 8.0 (Proxmox forum).

Where to move: five realistic targets

Target Fits when Watch out for
Proxmox VE (KVM) on your hardware 3+ hosts, a team that wants a GUI and clustering You own upgrades, storage (ZFS, Ceph) and support
OpenStack Many tenants, everything through an API Heavy to run; pays off only at scale
Hyper-V (Windows Server) Windows-heavy estate, Datacenter licences already paid Microsoft's tool needs Hyper-V drivers in Linux guests first
Cloud servers (KVM) A few to a few dozen VMs, no wish to run a hypervisor One import per VM, new IP addresses
Dedicated servers + your own hypervisor Steady load, you want the whole machine You run and patch the hypervisor yourself

Move a VM in five steps

  1. Inventory and clean up. Note firmware (BIOS or EFI), disk controller, NIC type, static IPs and any licence tied to a MAC address. Delete or consolidate snapshots so you export one current disk.
  2. Prepare the guest while it still runs on VMware. Windows: install the virtio drivers (virtio-win) now; a Windows guest moved to a virtio disk without them fails to boot. Linux: virtio drivers are in the kernel, but a host-only initramfs may leave them out; check with lsinitramfs or lsinitrd and rebuild it if needed.
  3. Export or connect. Shut the VM down and export an OVF / OVA (vSphere Client "Export OVF Template", or ovftool), or let virt-v2v or the Proxmox wizard pull it.
  4. Convert. Disk only with qemu-img, the whole guest with virt-v2v (it also adjusts drivers), or straight into Proxmox from the OVF.
  5. Boot on the console, then clean up. Check the NIC name and IP, remove VMware Tools or open-vm-tools, install qemu-guest-agent.
qemu-img convert -p -f vmdk -O qcow2 disk1.vmdk disk1.qcow2
virt-v2v -i ova VM.ova -o local -os /var/tmp -of qcow2
qm importovf 120 VM.ovf local-lvm

These are the documented forms from each tool's manual; we did not run these three here. The conversion we did run is below.

Tested: what a conversion actually does (ova2raw.js)

To show the moving parts, we wrote the conversion in about 100 lines of Bun with no dependencies. It opens an OVA (a plain tar), checks the SHA-256 manifest, prints the hardware the OVF asks for, unpacks the streamOptimized VMDK grain by grain into a sparse raw image and checks the GPT. We fed it Ubuntu's official 24.04 OVA, a real VMware-format export: noble-server-cloudimg-amd64.ova, build of 2026-10-17, whose SHA-256 matched Ubuntu's SHA256SUMS. Save as ova2raw.js:

// ova2raw.js - check a VMware OVA export and turn its disk into a raw image for KVM
// usage: bun ova2raw.js <export.ova> <out.raw>
import { openSync, readSync, writeSync, ftruncateSync, closeSync, fstatSync } from 'node:fs'
import { inflateSync } from 'node:zlib'
import { createHash } from 'node:crypto'

const [ova, out] = process.argv.slice(2)
const fd = openSync(ova, 'r')
const read = (pos, len) => {
  const b = Buffer.alloc(len)
  readSync(fd, b, 0, len, pos)
  return b
}

// 1. an OVA is a plain tar: list its members
const files = {}
for (let o = 0; o + 512 <= fstatSync(fd).size; ) {
  const h = read(o, 512)
  if (h.every(x => x === 0)) break
  const name = h.toString('utf8', 0, 100).replace(/\0.*$/s, '')
  const size = parseInt(h.toString('ascii', 124, 136).replace(/\0.*$/s, '').trim(), 8)
  files[name] = { pos: o + 512, size }
  o += 512 + Math.ceil(size / 512) * 512
}
const find = ext => Object.keys(files).find(n => n.endsWith(ext))
const text = n => read(files[n].pos, files[n].size).toString('utf8')
console.log('OVA members:', Object.entries(files).map(([n, f]) => `${n} (${f.size} B)`).join(', '))

// 2. the .mf manifest: every member's SHA-256 must match
const mf = find('.mf')
for (const line of mf ? text(mf).split('\n') : []) {
  const m = line.match(/^SHA(1|256)\((.+)\)\s*=\s*([0-9a-f]+)/i)
  if (!m || !files[m[2]]) continue
  const h = createHash('sha' + m[1])
  const f = files[m[2]]
  for (let p = 0; p < f.size; p += 1 << 24) h.update(read(f.pos + p, Math.min(1 << 24, f.size - p)))
  console.log(`  manifest ${m[2]}: ${h.digest('hex') === m[3].toLowerCase() ? 'OK' : 'MISMATCH'}`)
}

// 3. the .ovf descriptor: what the VM expects from its hardware
const ovf = text(find('.ovf'))
const items = [...ovf.matchAll(/<(?:ovf:)?Item\b[\s\S]*?<\/(?:ovf:)?Item>/g)].map(x => x[0])
const tag = (s, t) => s.match(new RegExp(`<(?:rasd:)?${t}>([^<]*)<`))?.[1]
const kind = { 3: 'CPU', 4: 'RAM', 5: 'IDE controller', 6: 'SCSI controller', 10: 'NIC', 17: 'disk', 20: 'SATA controller' }
console.log('OVF:', ovf.match(/vmw:osType="([^"]+)"/)?.[1] ?? '?', '| firmware',
  ovf.match(/key="firmware" ovf:value="([^"]+)"/)?.[1] ?? 'bios (default)')
for (const it of items) {
  const k = kind[tag(it, 'ResourceType')]
  if (k) console.log(`  ${k.padEnd(16)} ${tag(it, 'ElementName') ?? ''} ${tag(it, 'ResourceSubType') ?? ''} ${tag(it, 'VirtualQuantity') ?? ''}`.trimEnd())
}

// 4. the streamOptimized VMDK: a header, then compressed grains and markers
const vmdk = files[find('.vmdk')]
const hd = read(vmdk.pos, 512)
if (hd.readUInt32LE(0) !== 0x564d444b) throw new Error('not a sparse VMDK (no KDMV magic)')
const capacity = Number(hd.readBigUInt64LE(12)) * 512
const desc = read(vmdk.pos + Number(hd.readBigUInt64LE(28)) * 512, Number(hd.readBigUInt64LE(36)) * 512)
console.log(`VMDK ${desc.toString().match(/createType="([^"]+)"/)?.[1]}, virtual size ${capacity} B, grain ${Number(hd.readBigUInt64LE(20)) * 512} B`)

const ofd = openSync(out, 'w')
ftruncateSync(ofd, capacity) // sparse: unwritten grains stay holes = zeros
let pos = Number(hd.readBigUInt64LE(64)) * 512
let grains = 0
let written = 0
while (pos < vmdk.size) {
  const m = read(vmdk.pos + pos, 512)
  const val = Number(m.readBigUInt64LE(0))
  const size = m.readUInt32LE(8)
  if (size) { // compressed grain: lba, size, zlib data
    const raw = inflateSync(read(vmdk.pos + pos + 12, size))
    writeSync(ofd, raw, 0, raw.length, val * 512)
    grains++
    written += raw.length
    pos += Math.ceil((12 + size) / 512) * 512
  } else if (m.readUInt32LE(12) === 0) break // end-of-stream marker
  else pos += 512 + val * 512 // grain table / directory / footer: metadata only
}
closeSync(ofd)
console.log(`Wrote ${out}: ${grains} grains, ${written} B of data in ${capacity} B`)

// 5. check the result: a valid GPT, and the file systems where the partitions say they are
const rfd = openSync(out, 'r')
const at = (p, l) => { const b = Buffer.alloc(l); readSync(rfd, b, 0, l, p); return b }
const gpt = at(512, 92)
const crc = Buffer.from(gpt)
crc.writeUInt32LE(0, 16)
console.log(`GPT "${gpt.toString('ascii', 0, 8)}", header CRC ${(Bun.hash.crc32(crc) >>> 0) === gpt.readUInt32LE(16) ? 'OK' : 'BAD'}`)
const n = gpt.readUInt32LE(80)
const es = gpt.readUInt32LE(84)
const ents = at(Number(gpt.readBigUInt64LE(72)) * 512, n * es)
console.log(`  partition array CRC ${(Bun.hash.crc32(ents) >>> 0) === gpt.readUInt32LE(88) ? 'OK' : 'BAD'}`)
for (let i = 0; i < n; i++) {
  const e = ents.subarray(i * es, (i + 1) * es)
  if (e.subarray(0, 16).every(x => x === 0)) continue
  const first = Number(e.readBigUInt64LE(32))
  const last = Number(e.readBigUInt64LE(40))
  const sb = at(first * 512 + 1024, 136)
  const fs = sb.readUInt16LE(56) === 0xef53 ? `ext4 label "${sb.toString('ascii', 120, 136).replace(/\0.*$/s, '')}"`
    : at(first * 512, 512).toString('ascii', 82, 87) === 'FAT32' ? 'FAT32 (EFI system partition)'
    : e.toString('ascii', 0, 16) === 'Hah!IdontNeedEFI' ? 'BIOS boot (GRUB for legacy BIOS)' : 'unknown'
  console.log(`  p${i + 1} ${String((last - first + 1) * 512 / 2 ** 20 | 0).padStart(5)} MiB  ${fs}`)
}
bun ova2raw.js noble-server-cloudimg-amd64.ova noble.raw

Output, run on a DCXV Cloud server with Bun 1.4.4 on 2026-11-03:

OVA members: ubuntu-noble-24.04-cloudimg.ovf (11186 B), ubuntu-noble-24.04-cloudimg.mf (213 B), ubuntu-noble-24.04-cloudimg.vmdk (590749696 B)
  manifest ubuntu-noble-24.04-cloudimg.vmdk: OK
  manifest ubuntu-noble-24.04-cloudimg.ovf: OK
OVF: ubuntu64Guest | firmware bios (default)
  CPU              2 virtual CPU(s)  2
  RAM              1024MB of memory  1024
  SCSI controller  SCSI Controller 0 VirtualSCSI
  IDE controller   VirtualIDEController 1
  IDE controller   VirtualIDEController 0
  disk             Hard Disk 1
  NIC              Ethernet 1 VmxNet3
VMDK streamOptimized, virtual size 10737418240 B, grain 65536 B
Wrote noble.raw: 28663 grains, 1878458368 B of data in 10737418240 B
GPT "EFI PART", header CRC OK
  partition array CRC OK
  p1  2558 MiB  ext4 label "cloudimg-rootfs"
  p14     4 MiB  BIOS boot (GRUB for legacy BIOS)
  p15   106 MiB  FAT32 (EFI system partition)
  p16   913 MiB  ext4 label "BOOT"

Three runs took 14.6 to 16.2 s wall clock on that server. The raw file is 10 GiB in size and 1.8 GB on disk.

What the output tells you before cutover

  1. VmxNet3 and VirtualSCSI are VMware devices. On KVM they become virtio-net and virtio-blk or virtio-scsi. The guest needs drivers for both before the first boot, and the network interface usually gets a new name, so a static IP bound to the old name will not come up. Use the console for the first boot.
  2. Firmware must match. This OVF sets nothing, so it is BIOS; the disk carries both a BIOS boot and an EFI system partition, so it boots either way. A VM exported with firmware=efi needs UEFI (OVMF) on the target, otherwise it finds no boot loader.
  3. Thin stays thin only if you keep it so. 10 GiB virtual, 1.8 GB of data. Copy with sparse-aware tools (cp --sparse=always, rsync -S) or use qcow2, or you move and store zeros.
  4. The manifest is your transfer check. Verify the SHA-256 after every copy between sites: a cut-off upload converts without complaint into a disk that fails later.

Plan the downtime

A cold move costs export + transfer + convert + first boot. Time each on one test VM, then multiply. Transfer arithmetic (ours, before protocol overhead): 1.8 GB at 1 Gbit/s is about 15 s, 1 TB about 2.2 hours. Warm tools copy while the VM runs and send only changed blocks at the end; The Register's testers report cold and warm runs with Forklift and MTV. For databases and busy apps, replication plus a DNS switch beats any disk copy: lower the TTL first (see DNS propagation and TTL) and follow the checklists in how to migrate to a cloud server.

When DCXV cloud or dedicated servers fit

  • A handful of VMs, no hypervisor team: our cloud servers run on KVM with virtio disks. In the panel, Reinstall -> "Custom image (URL)" takes a qcow2, raw or vmdk file over https; an image larger than the plan disk raises the disk and is billed as a disk upgrade, and network settings are applied only if the image has cloud-init. Plans start at EUR 15.00/mo (2 vCPU, 4 GB RAM, 40 GB; checked 2026-11-03, excl. VAT).
  • Dozens of VMs or your own Proxmox cluster: dedicated servers from EUR 210/mo, with the hypervisor you choose.
  • Windows guests: we resell Windows Server licences; ask before the move so the licence is ready on day one.

Both run in our Tier III data centres in Prague and Covilha.

Plan the move with us

Run ova2raw.js on one exported VM and send the hardware lines of its output to sales@dcxv.com, or start a cloud server and test the import yourself.

Leaving VMware After Broadcom: A Checked Migration Guide
clouddedicatedinfrastructure

Leaving VMware After Broadcom: A Checked Migration Guide

Broadcom ended perpetual licences, cut partners and hid VDDK. How EU teams move VMs to KVM, Proxmox or Hyper-V, with dated facts and a tested OVA converter.

DNS Propagation Time: Cloudflare's 3 Seconds vs Your TTL
networkingcloudinfrastructure

DNS Propagation Time: Cloudflare's 3 Seconds vs Your TTL

Cloudflare cut its DNS build delay to about 3.3 s. A cutover still waits on TTLs, negative caching and a 2-day .com delegation. We measured them on two zones.

What 10,000 US Council Comments Mean for Data Center Buyers
clouddedicatedinfrastructure

What 10,000 US Council Comments Mean for Data Center Buyers

A new dataset codes 10,202 US council comments on data centers: 90.5% opposed, water first. We recount it and show what it means for server buyers in Europe.

AWS Support and Billing Pain: A Checked Guide for EU Teams
cloudpricingcompare

AWS Support and Billing Pain: A Checked Guide for EU Teams

AWS paid support starts at $29 or 9% of the bill, billing data lags up to 24 h, Frankfurt egress is $0.09/GB. A checked guide with a cost script for EU teams.

Memory Latency on a Cloud Server: Measured, Not Guessed
clouddedicatedinfrastructure

Memory Latency on a Cloud Server: Measured, Not Guessed

A viral post says a busy CPU is often just waiting on RAM. We measured L1-to-DRAM latency on our own cloud server and show what it means when you rent one.