Incus imports image as Container instead of Virtual Machine

Hi community!
I’m trying to import a virtual machine QCOW2 from Home Assistant (https://github.com/home-assistant/operating-system/releases/download/10.1/haos_ova-10.1.qcow2.xz)
I’m doing exactly my metadata.yaml and my incus image import as mentioned here (for LXD…): How to Run Home Assistant OS in a Virtual Machine under LXD | Sean Blanchfield
The issue is that once imported the OVA is recognized as a container image by incus image list. I read the whole documentation and I’m failing to find how to force it to be considered a Virtual Machine. The VM needs to use Docker itself, so I can’t have it be an unprivileged container (and I don’t want to set it up as privileged).
I realize it may be a stupid question, but I don’t understand what makes the OVA a container-type image, and how I could fix this? I would really appreciate your help with this!

Edit: I’m particularly confused because I can easily choose VM or container on incus-migrate, to create an instance, then incus publish the image as virtual machine, which works great. Sadly incus-migrate cannot be used in a non-interactive way for use in a script, so even this hack wouldn’t work

That is a very good question. I tried doing exports of ubuntu/24.04/cloud (container) and ubuntu/24.04/cloud (--vm version) and the metadata was exactly identical. The only difference was that one had a qcow2 root image, and one a squashfs root image.

After deleting the images with incus image delete, re-importing works fine. There is no --vm flag to incus image import, nor did I attempt to provide it.

But I think I found the solution in the source, in cmd/incus/image.go:

                        _, ext, _, err := archive.DetectCompressionFile(rootfs)
...
                        if ext == ".qcow2" {
                                imageType = "virtual-machine"
                        }

In turn, DetectCompressionFile looks at the first three bytes of the file:

        case bytes.Equal(header[0:3], []byte{'Q', 'F', 'I'}):
                return []string{""}, ".qcow2", []string{"qemu-img", "convert", "-O", "raw"}, nil

Therefore, your qcow2 file just needs the correct magic start:

root@nuc3:/var/tmp# head -c3 vm.txz.root | hexdump -C
00000000  51 46 49                                          |QFI|
00000003

(Note: qemu-img info will always show a compression type. This is because compression is possible for individual clusters within the file, but it doesn’t mean any clusters are in fact compressed. More info here)

EDIT: Tested with the image you gave, and this doesn’t appear to be the problem:

# xzcat haos_ova-10.1.qcow2.xz >haos_ova-10.1.qcow2
# head -c3 haos_ova-10.1.qcow2 | hexdump -C
00000000  51 46 49                                          |QFI|
00000003

In fact, this image imports just fine as a VM:

root@nuc3:/var/tmp# tar -cvzf metadata.tar.gz metadata.yaml
metadata.yaml
root@nuc3:/var/tmp# incus image import metadata.tar.gz haos_ova-10.1.qcow2 --alias haos
Image imported with fingerprint: 0781ff46f23e917c05bfaa5e2069cf9ca02d9c7cee3237de38b6967feaf5a02d
root@nuc3:/var/tmp# incus image list
+-------+--------------+--------+----------------------+--------------+-----------------+-----------+----------------------+
| ALIAS | FINGERPRINT  | PUBLIC |     DESCRIPTION      | ARCHITECTURE |      TYPE       |   SIZE    |     UPLOAD DATE      |
+-------+--------------+--------+----------------------+--------------+-----------------+-----------+----------------------+
| haos  | 0781ff46f23e | no     | Home Assistant image | x86_64       | VIRTUAL-MACHINE | 979.63MiB | 2025/02/01 20:20 UTC |
+-------+--------------+--------+----------------------+--------------+-----------------+-----------+----------------------+

So I don’t observe the problem you originally described. I am using incus 6.0.3 (LTS).

Is it possible you forgot to uncompress the .xz to give the .qcow2 file?

This is just an aside, but you should be able to run Docker inside an unprivileged container by turning on security.nesting

Thank you so much! It indeed works by decompressing the .xz before importing. I thought based on Image format - Incus documentation that providing the .xz would work, but reading it again it only references a compressed tar. You saved me so much time, I appreciate it! I’ll also explore the unprivileged nesting-enabled container option, but it seems it may need some AppArmor fine-tuning.