# The build VM image. Hearthforge starts one container of it per build_image # step. The container runs QEMU, and the VM inside runs buildah. See CI.md. # Hearthforge embeds this directory and builds the image on first use with # the engine's classic build API, so it must not need BuildKit. ARG ALPINE=docker.io/library/alpine:3.24 # The system that boots inside the VM. FROM ${ALPINE} AS guest RUN apk add --no-cache buildah crun netavark e2fsprogs tar ca-certificates COPY guest/ / # The embedded copy loses file modes. RUN chmod 755 /init # --no-scripts skips the mkinitfs trigger. Its initramfs is not used. FROM ${ALPINE} AS kernel RUN apk add --no-cache --no-scripts linux-virt kmod # Modules are stored uncompressed and numbered in load order, so the guest # init needs only busybox insmod. RUN set -eu; \ kver=$(ls /lib/modules); \ mkdir -p /out/modules; \ for m in virtio_blk virtio_net ext4 overlay virtio_rng; do \ modprobe -S "$kver" --show-depends "$m"; \ done | awk '$1 == "insmod" && !seen[$2]++ { print $2 }' > /tmp/modules; \ i=0; \ while read -r ko; do \ i=$((i + 1)); \ gunzip -c "$ko" > "/out/modules/$(printf %02d $i)-$(basename "$ko" .gz)"; \ done < /tmp/modules; \ cp /boot/vmlinuz-virt /out/vmlinuz FROM ${ALPINE} AS initramfs RUN apk add --no-cache cpio COPY --from=guest / /rootfs/ COPY --from=kernel /out/modules/ /rootfs/lib/hf-modules/ RUN set -eu; \ cd /rootfs; \ mkdir -p proc sys dev tmp run var/lib/containers; \ find . -print0 | cpio --null --quiet -o -H newc | gzip -1 > /initramfs.gz FROM ${ALPINE} RUN apk add --no-cache "qemu-system-$(apk --print-arch)" tar COPY --from=kernel /out/vmlinuz /vm/vmlinuz COPY --from=initramfs /initramfs.gz /vm/initramfs.gz COPY run-vm /usr/local/bin/run-vm RUN chmod 755 /usr/local/bin/run-vm && mkdir -p /in/context /out /work ENTRYPOINT ["/usr/local/bin/run-vm"]