├── .github └── workflows │ ├── .dockerignore │ ├── docker-publish.yml │ └── docker-update-docs.yml ├── Dockerfile ├── README.md └── renovate.json /.github/workflows/.dockerignore: -------------------------------------------------------------------------------- 1 | .github -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish Docker image 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | push: 7 | branches: [ master ] 8 | paths: 9 | - 'Dockerfile' 10 | 11 | env: 12 | DOCKER_HUB_TAG: ${{ secrets.DOCKER_HUB_USERNAME }}/qemu-ga 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v3 24 | 25 | # Login against a Docker registry 26 | # https://github.com/docker/login-action 27 | - name: Log into Docker Hub 28 | uses: docker/login-action@v3 29 | with: 30 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 31 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 32 | 33 | # Build and to Docker 34 | # https://github.com/docker/build-push-action 35 | - name: Build docker image 36 | uses: docker/build-push-action@v6 37 | with: 38 | context: . 39 | load: true 40 | tags: ${{ env.DOCKER_HUB_TAG }}:getversion 41 | 42 | # Get version of qemu-ga in built container 43 | - name: Get qemu-ga version 44 | run: > 45 | echo "QEMU_GA_VERSION=$( 46 | docker run --rm 47 | ${{ env.DOCKER_HUB_TAG }}:getversion 48 | qemu-ga --version | 49 | grep -Po --color=never '\d+\.*\d*\.*\d*$')" >> $GITHUB_ENV 50 | 51 | - name: Docker meta tags 52 | id: meta 53 | uses: docker/metadata-action@v5 54 | with: 55 | images: ${{ env.DOCKER_HUB_TAG }} 56 | tags: | 57 | type=raw,value=${{ env.QEMU_GA_VERSION }} 58 | flavor: | 59 | latest=true 60 | 61 | # Build and push image using qemu-ga version as tag 62 | - name: Build and push 63 | uses: docker/build-push-action@v6 64 | with: 65 | platforms: linux/amd64,linux/arm64 66 | context: . 67 | push: true 68 | tags: ${{ steps.meta.outputs.tags }} 69 | labels: ${{ steps.meta.outputs.labels }} 70 | -------------------------------------------------------------------------------- /.github/workflows/docker-update-docs.yml: -------------------------------------------------------------------------------- 1 | name: Update Docker Hub Description 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | push: 7 | branches: [ master ] 8 | paths: 9 | - 'README.md' 10 | 11 | env: 12 | DOCKER_HUB_TAG: ${{ secrets.DOCKER_HUB_USERNAME }}/qemu-ga 13 | 14 | jobs: 15 | docker: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Docker Hub Description 22 | uses: peter-evans/dockerhub-description@v4 23 | with: 24 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 25 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 26 | repository: ${{ env.DOCKER_HUB_TAG }} -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.22 2 | LABEL maintainer="dskadra@gmail.com" 3 | 4 | RUN apk add --update --no-cache qemu-guest-agent 5 | 6 | ENTRYPOINT [ "/usr/bin/qemu-ga" ] 7 | CMD ["-m", "virtio-serial", "-p", "/dev/virtio-ports/org.qemu.guest_agent.0"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QEMU Guest Agent Container Image 2 | 3 | ## Overview 4 | 5 | This container is designed to run on a minimal container operating system like CoreOS or 6 | Flatcar Linux, running under QEMU/KVM, Proxmox, or other libvirt based virtual machine. 7 | These operating systems often don't have a package management system to easily install the agent. 8 | 9 | ## Quick Start 10 | 11 | Enable the QEMU Guest Agent Channel in the VM configuration. 12 | | VM Host | Enable Guest Agent | 13 | | ------------------------------ | ------------------------------------------------------------- | 14 | | QEMU / Virtual Machine Manager | Add the `Guest Agent Channel` device (org.qemu.guest_agent.0) | 15 | | Proxmox | Enable the `QEMU Guest Agent` option | 16 | 17 | ### Docker on Fedora CoreOS 18 | 19 | ```bash 20 | docker run --rm -d --name qemu-ga \ 21 | -v /etc/os-release:/etc/os-release:ro \ 22 | --device /dev/virtio-ports/org.qemu.guest_agent.0:/dev/virtio-ports/org.qemu.guest_agent.0 \ 23 | --net=host \ 24 | --uts=host \ 25 | docker.io/danskadra/qemu-ga 26 | ``` 27 | 28 | This will allow the Guest Agent to retrieve the OS information, host name, and IP addresses of the 29 | container's Host VM. 30 | 31 | ## Advanced Functionality 32 | 33 | If more functionality is required from the Guest Agent, such as reboot and shutdown of non-ACPI VMs, 34 | the following can be used. (This example is for CoreOS) 35 | 36 | ```bash 37 | docker run --rm -d --name qemu-ga \ 38 | -v /dev:/dev \ 39 | -v /etc/os-release:/etc/os-release:ro \ 40 | -v /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket \ 41 | -v /sys/fs/cgroup:/sys/fs/cgroup \ 42 | -v /sbin/shutdown:/sbin/shutdown \ 43 | -v /bin/systemctl:/bin/systemctl \ 44 | -v /usr/lib/systemd:/usr/lib/systemd \ 45 | -v /lib64:/lib64 \ 46 | --privileged \ 47 | --uts=host \ 48 | --net=host \ 49 | docker.io/danskadra/qemu-ga -v 50 | ``` 51 | 52 | ⚠ **This is less secure, since the container is now running in privileged mode and generally has 53 | full access to the host VM.** 54 | 55 | QEMU Guest Agent falls back on using the `/sbin/shutdown` command to execute reboots and shutdowns. 56 | On a Systemd based OS like CoreOS, `shutdown` is symlinked to the `systemctl` command. For the container 57 | to execute these commands, they need to be mapped into the container as well as the socket and cgroup 58 | dependencies that Systemd requires. 59 | 60 | ```bash 61 | -v /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket \ 62 | -v /sys/fs/cgroup:/sys/fs/cgroup \ 63 | -v /sbin/shutdown:/sbin/shutdown \ 64 | -v /bin/systemctl:/bin/systemctl \ 65 | ``` 66 | 67 | ## Resolving Dependency Issues 68 | 69 | There is a problem with bind mounting commands into a container when the host's OS and the container's 70 | base OS don't match. Compiled commands are linked against specific versions of GCC and other library 71 | files. The versions of these library files vary from distribution to distribution and even from 72 | different versions of the same distribution. 73 | 74 | In the example above, this means that the mounted systemctl command from CoreOS into this Alpine 75 | based container will not run because the container is missing the dependant libraries from the host VM. 76 | 77 | The hacky solution is to bind mount the required libraries into the container as well. Use the `ldd` 78 | command to discover the mounted command's dependencies. (Run this on the host VM, not the container) 79 | 80 | ```bash 81 | ldd /bin/systemctl 82 | linux-vdso.so.1 (0x00007ffd2117d000) 83 | libsystemd-shared-251.11-2.fc37.so => /usr/lib/systemd/libsystemd-shared-251.11-2.fc37.so (0x00007f8d1d600000) 84 | libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f8d1da85000) 85 | libc.so.6 => /lib64/libc.so.6 (0x00007f8d1d423000) 86 | libacl.so.1 => /lib64/libacl.so.1 (0x00007f8d1da7b000) 87 | libblkid.so.1 => /lib64/libblkid.so.1 (0x00007f8d1da42000) 88 | libcap.so.2 => /lib64/libcap.so.2 (0x00007f8d1da36000) 89 | libcrypt.so.2 => /lib64/libcrypt.so.2 (0x00007f8d1d9fc000) 90 | libkmod.so.2 => /lib64/libkmod.so.2 (0x00007f8d1d9de000) 91 | liblz4.so.1 => /lib64/liblz4.so.1 (0x00007f8d1d9bb000) 92 | libmount.so.1 => /lib64/libmount.so.1 (0x00007f8d1d974000) 93 | libcrypto.so.3 => /lib64/libcrypto.so.3 (0x00007f8d1ce00000) 94 | libp11-kit.so.0 => /lib64/libp11-kit.so.0 (0x00007f8d1d2ee000) 95 | libpam.so.0 => /lib64/libpam.so.0 (0x00007f8d1d960000) 96 | libseccomp.so.2 => /lib64/libseccomp.so.2 (0x00007f8d1d2ce000) 97 | libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f8d1d2a1000) 98 | libzstd.so.1 => /lib64/libzstd.so.1 (0x00007f8d1cd4b000) 99 | liblzma.so.5 => /lib64/liblzma.so.5 (0x00007f8d1d26d000) 100 | libm.so.6 => /lib64/libm.so.6 (0x00007f8d1cc6b000) 101 | /lib64/ld-linux-x86-64.so.2 (0x00007f8d1daf1000) 102 | libattr.so.1 => /lib64/libattr.so.1 (0x00007f8d1d956000) 103 | libz.so.1 => /lib64/libz.so.1 (0x00007f8d1d253000) 104 | libffi.so.8 => /lib64/libffi.so.8 (0x00007f8d1d247000) 105 | libaudit.so.1 => /lib64/libaudit.so.1 (0x00007f8d1cc3d000) 106 | libeconf.so.0 => /lib64/libeconf.so.0 (0x00007f8d1d23c000) 107 | libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007f8d1cba0000) 108 | libcap-ng.so.0 => /lib64/libcap-ng.so.0 (0x00007f8d1d230000) 109 | ``` 110 | 111 | In this case, this version of CoreOS's systemctl requires libraries located in `/lib64` and 112 | `/usr/lib/systemd/libsystemd-shared-251.11-2.fc37.so`. Because the Alpine container doesn't have 113 | the `/lib64` or `usr/lib/systemd` directories, it's safe and easier to mount the whole directory 114 | from the host VM instead of each library individually. 115 | 116 | ```bash 117 | -v /sbin/shutdown:/sbin/shutdown 118 | -v /bin/systemctl:/bin/systemctl 119 | -v /usr/lib/systemd:/usr/lib/systemd 120 | -v /lib64:/lib64 121 | ``` 122 | 123 | **Note:** If the host VM's shutdown command or systemctl are linked to libraries in the `/lib` or `/usr/lib` 124 | directories, which exist in the Alpine container, it may be necessary to bind mount each individual 125 | dependant library it to the same directory in the container. 126 | 127 | ## Useful Options 128 | 129 | | Option | Descriptopn | 130 | | ---------------------------------------------------- | ---------------------------------------------------- | 131 | | `-v /etc/os-release:/etc/os-release:ro` | Read only access to VM OS info | 132 | | `--device [VirtIO Serial Port]:[VirtIO Serial Port]` | (**Required**) Agent communication to host VM | 133 | | `--uts=host` | Allows Guest Agent to read VM hostname | 134 | | `--net=host` | Allows Guest Agent to read network info from host VM | 135 | 136 | ## Security Considerations 137 | 138 | The QEMU Guest Agent is designed to interact directly with the host operating system. 139 | To allow access to the host while running the Guest Agent inside of a container, the 140 | container may be run with extended capabilities. Generally this is accomplished by 141 | using the `--privileged` command option. This grants far more capabilities to the 142 | container than is needed by the use case presented here. i.e. Retrieving IP addresses, 143 | host names, OS versions, etc, for visibility to the KVM host. 144 | 145 | Security can be improved by replacing the `--privileged` option with the `--device` 146 | option and bind mounting a volumes to specific files. This can be used to limit access 147 | to only the VirtIO Guest Agent device, instead of all the devices in /dev or other 148 | capabilities granted by `--privileged`. 149 | 150 | If more interaction with the host VM is required, such as rebooting and shutting down the VM, more access 151 | will be required. This will be by either using the `--privileged` option or using the `--cap-add` option 152 | to add specific capabilities to the container. 153 | 154 | ## Additional Info 155 | 156 | - [Container GitHub Repo](https://github.com/dskad/qemu-ga-container) 157 | - [Docker Hub Repo](https://hub.docker.com/r/danskadra/qemu-ga) 158 | - [QEMU Guest Agent Protocol Reference](https://qemu.readthedocs.io/en/latest/interop/qemu-ga-ref.html) 159 | - [Docker Run Reference](https://docs.docker.com/engine/reference/run/) -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | --------------------------------------------------------------------------------