Flatten files, not a running container
The build creates a stopped container from the selected image and exports its filesystem as a tar stream. No container workload needs to run. The VM later boots that userspace under its own Linux kernel, not the Docker host's kernel.
- DOCKER_TAG names the source image. If DOCKERFILE is set, first build it from the repository-root context. Create a temporary stopped container from that image.
- docker export provides the container's root filesystem, not image-layer history, runtime metadata, or the contents of mounted volumes.
- Create a sparse file, format ext4 with lazy initialization disabled, loop-mount it, and extract the tar stream into its root.
- Sync, unmount, run e2fsck, and remove the temporary container. Attach the resulting image directly or use it as an immutable CoW base.
Build on a disposable Linux workspace
You need Docker, sudo, loop-mount support and ext4 tools (mkfs.ext4, e2fsck). Run from the repository root. Create the output directory and a dedicated, empty mount point first; the target does not do that for you.
mkdir -p ./disks ./rootfs-mount
make build-disk \
DOCKER_TAG=kube-vm-rootfs \
DOCKERFILE=./Dockerfiles/Dockerfile_ubuntu \
DISK_DIR=./disks \
DISK_IMG=base.img \
DISK_SIZE=2500M \
MOUNT_DIR="$(pwd)/rootfs-mount"
Omit DOCKERFILE to use an image already available to Docker. Defaults are DISK_DIR=disks, DISK_IMG=kube-vm-disk.img, DISK_SIZE=2500M and MOUNT_DIR=/mnt. A sparse file has the requested logical capacity without eagerly allocating all of its host blocks; populated files and metadata consume real space.
A container rootfs is not automatically bootable
Supply init and drivers deliberately
Docker's CMD and ENTRYPOINT metadata are not executed by Linux when it mounts this disk. The root filesystem must provide an init process, such as /sbin/init, or you must specify a suitable init= argument. Likewise, guest networking and SSH must be configured as guest services.
The included Ubuntu Dockerfile installs systemd and SSH; the Kubernetes variant adds cluster tooling. These are starting points for experiments, not hardened images. Review account credentials and privilege settings before any network exposure.
Whole disk means /dev/vda
The target formats the entire file as ext4. There is no partition table, EFI partition or bootloader. For the first attached virtio block disk use root=/dev/vda, not /dev/vda1. Attach additional devices deliberately; changing their order can change Linux device names.
./bin/kube-vm -server=false \
-kernel ./bzImage \
-cow-block ./disks/base.img=./disks/test.overlay \
-cmdline "console=hvc0 root=/dev/vda rw init=/sbin/init"
This no-initrd example requires the kernel to have virtio-MMIO, virtio block, the chosen console and ext4 built in. If those are modules, provide an initrd that loads them before mounting root. The kernel must also be compatible with the image's userspace architecture.
Build once, give each VM a private future
Once the base is checked and unmounted, keep it unchanged and use a different CoW overlay for each VM. Docker layers are flattened during export; kube-vm's overlay is a separate block-level mechanism, not Docker's filesystem layer stack.
If you later need the running machine's memory and execution state too, take a full checkpoint. A root disk alone restarts the operating system; a checkpoint resumes the captured machine.
build-disk recipeUbuntu rootfs recipeKubernetes rootfs recipe