Detect Linux Virtualisation with systemd-detect-virt

Scripts that behave differently in a VM, container or chroot need to know which one they're in, and systemd-detect-virt answers that in one call. It also covers a chroot or a user namespace, not just full-blown virtualisation. The examples use systemd-detect-virt from systemd 255.4-1ubuntu8.17, installed here as systemd 255. Allow about ten minutes. You need a shell and the systemd package; the normal checks are read-only and do not need elevated privileges.

The command reports two separate things: a short identifier on standard output, and an exit status. A detected environment returns status 0. No matching environment returns a non-zero status. Treat the status as the scriptable result and the identifier as useful context for a person reading the output.

1. Confirm the installed command

Check the binary and version before relying on option details. This is an ordinary, read-only check:

$ command -v systemd-detect-virt
/usr/bin/systemd-detect-virt
$ systemd-detect-virt --version
systemd 255 (255.4-1ubuntu8.17)

Your build features can differ after the version line. The options used below are present in the installed systemd 255 manpage. The command has no configuration file to edit for these checks, so there is no service restart or undo step.

Checkpoint: If the command is missing, stop here and install or repair the systemd package through your normal operating system process. Do not replace it with an unrelated container utility when you need systemd's detection rules.

2. Ask for the innermost environment

Run the command without a filter when you want the most immediate virtualisation layer:

$ systemd-detect-virt
none
$ printf 'exit status: %s\n' "$?"
exit status: 1

On this host, none means no supported virtualisation was detected and status 1 confirms that result. On a KVM guest, the output might be kvm; in a Docker container it might be docker. The exact identifier depends on the host and runtime.

If machine and container virtualisation are nested, the command normally reports the innermost layer. A container running inside a VM therefore reports the container, not necessarily the hypervisor. Use a filter when your decision concerns the outer machine instead.

3. Separate containers from virtual machines

Use --container for shared-kernel environments and --vm for full machine virtualisation. Add --quiet when a script only needs the Boolean result:

$ if systemd-detect-virt --container --quiet; then
>     echo 'container detected'
> else
>     echo 'no container detected'
> fi
no container detected
$ if systemd-detect-virt --vm --quiet; then
>     echo 'virtual machine detected'
> else
>     echo 'no virtual machine detected'
> fi
no virtual machine detected

--quiet suppresses the identifier, but it does not change the exit-status contract. Do not test the text none in a script when the status already gives you the correct branch. If you need a reason for a positive result, run the same check again without --quiet.

4. See the identifiers supported by this build

--list prints the environments that this installed command knows how to detect. It does not claim that any of them is present:

$ systemd-detect-virt --list
none
kvm
amazon
qemu
bochs
xen
uml
vmware
oracle
microsoft
zvm
parallels
bhyve
qnx
acrn
powervm
apple
sre
google
vm-other
systemd-nspawn
lxc-libvirt
lxc
openvz
docker
podman
rkt
wsl
proot
pouch
container-other

The list is a capability list, not a live inventory. It can also change with a newer systemd release. Compare identifiers as exact strings and allow for an unknown or newer value rather than assuming this list is exhaustive forever.

5. Check chroots and user namespaces separately

Use --chroot to ask whether the process was invoked in a chroot() environment. Use --private-users to ask whether it is inside a user namespace. These modes write no detection identifier; the exit status carries the answer:

$ if systemd-detect-virt --chroot; then
>     echo 'chroot detected'
> else
>     echo 'no chroot detected, or the check failed'
> fi
$ if systemd-detect-virt --private-users; then
>     echo 'user namespace detected'
> else
>     echo 'no user namespace detected, or the check failed'
> fi

Keep an error distinct from an ordinary negative result when the check is part of a security decision. On this host, an unprivileged --chroot invocation reports Permission denied and returns 1, so a bare if statement cannot tell those cases apart. Capture standard error and inspect the message, or run the read-only diagnostic with the privilege required by your host. Do not grant extra capabilities merely to make a monitoring script convenient.

6. Treat confidential VM detection as a hint

--cvm checks for a confidential virtual machine. Its result can help disable features that are unsuitable there, but the manpage explicitly warns that it must not be used to release sensitive information. Release secrets only after an appropriate attestation process:

$ systemd-detect-virt --cvm
none
$ printf 'exit status: %s\n' "$?"
exit status: 1

The output and status above are from this host, which is not detected as a confidential VM. The check is not attestation and does not prove that a machine has a trustworthy confidential environment.

7. Build a safe pre-flight check

For a service or script, choose the narrowest test that matches the requirement and handle a failure explicitly. This example refuses to continue in a container while preserving the detected identifier for diagnostics:

virt_id=$(systemd-detect-virt 2>virt-check.err)
virt_status=$?
if [ "$virt_status" -eq 0 ]; then
    printf 'virtualisation: %s\n' "$virt_id"
else
    printf 'no supported virtualisation detected\n'
fi
if [ "$virt_status" -ne 0 ] && [ -s virt-check.err ]; then
    cat virt-check.err >&2
fi
rm -f virt-check.err

This changes no system state, but the temporary error file is still unnecessary clutter if the command is known to be readable. In a longer-lived script, use a private temporary directory and arrange cleanup on every exit path. Never use a positive virtualisation result as proof that a secret can be disclosed, and do not use a negative result as proof that the host is bare metal if the command reported an error.

Done means