bootimage: turning a Rust kernel build into a bootable disk image with one cargo subcommand
Tool to create bootable disk images from a Rust OS kernel.
At a glance
- What is it?
- bootimage is a Rust tool that packages a kernel binary with a bootloader into a bootable disk image, and it installs as a cargo subcommand so the whole flow becomes cargo bootimage and cargo xrun. It is small, dependency-light, and it also runs your kernel's unit and integration tests inside QEMU.
- Who is it for?
- bootimage suits a Rust OS developer who already has a kernel target and wants the bootloader, the image build and the test harness handled by the standard cargo workflow rather than a pile of shell scripts. It does not suit someone starting from nothing, since it assumes a bare-metal target and a working kernel build, and it does not suit a project that needs a different boot chain, because it is built around the rust-osdev bootloader crate.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 81 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem it removes is the shell script that assembles a boot image
bootimage creates a bootable disk image from a Rust OS kernel. The description in the Cargo manifest is more terse, calling it a tool to create a bootable OS image from a kernel binary. What it removes is a piece of infrastructure that every bare-metal Rust project ends up writing for itself. To boot a kernel you need a bootloader placed at the right place on a disk, the kernel image written into the right partition, and then some way to hand that disk to a virtual machine. Done by hand that is a chain of objcopy invocations, a partition layout, and a QEMU command line, usually captured in a shell script that nobody wants to maintain. bootimage turns that into a cargo subcommand, so the build step reads like every other cargo build, and the run step reads like cargo run. The audience is narrow and clear: people writing operating systems in Rust with a custom target, who are past the hello-world stage and into the part where a bootable artefact is needed.
It installs as a cargo subcommand, so the whole flow is cargo build, cargo bootimage, cargo xrun
Installation is one command:
cargo install bootimageand usage follows the same pattern. Your kernel's Cargo.toml needs a dependency on the bootloader crate:
# in your Cargo.toml
[dependencies]
bootloader = "0.9.8"With that in place, building the kernel and producing the image is a single invocation:
cargo bootimage --target your_custom_target.json [other_args]The command invokes cargo build and forwards all passed options, then builds the specified bootloader together with the kernel to create the bootable disk image. That last clause is the mechanism worth understanding. The bootloader is not a blob you commit or a file you download; it is compiled alongside your kernel and packaged with it, which means a change in the kernel and a change in the bootloader are always built from the same source tree. For running, you set a custom runner in a .cargo/config file:
[target.'cfg(target_os = "none")']
runner = "bootimage runner"and then cargo xrun --target your_custom_target.json hands the image to QEMU, with everything after -- passed to QEMU.
Version pairing is the first thing to get right
The README carries a version note that saves a confusing afternoon. At least bootloader version 0.5.1 is required since bootimage 0.7.0, and for earlier bootloader versions you should use bootimage 0.6.6. That pairing rule matters because both crates move independently, and a mismatch produces a build failure or a subtly wrong image rather than a clear error. The current manifest lists bootimage at version 0.10.5, on edition 2018, with the repository at github.com/rust-osdev/bootimage and the author as Philipp Oppermann. The example dependency shown in the README is bootloader 0.9.8, which sits well inside the supported range. There is also a documented escape hatch: if you want to use a custom bootloader with a different name, you can use Cargo's rename functionality, which lets you depend on the crate under another identifier without patching the source.
Configuration is a Cargo.toml table, so the project definition stays in the project
Configuration lives in a [package.metadata.bootimage] table in the Cargo.toml of your kernel. The build-command option sets the cargo subcommand used for building the kernel, with a comment noting it should be set to xbuild when building with the cargo-xbuild crate. The run-command is the command invoked with the created bootimage, where the {} placeholder is replaced with the path to the bootable disk image, and it applies to both bootimage run and bootimage runner. Then run-args holds additional arguments for non-test executables, and test-args holds additional arguments for test executables, the latter applying only to bootimage runner. The remaining three options are test-specific: test-success-exit-code is an exit code to treat as success for test executables, test-timeout is the timeout in seconds for running a test through bootimage test or bootimage runner, with a default of 300, and test-no-reboot controls whether the -no-reboot flag is passed to test executables. Putting all of this in the manifest rather than in a script means the build recipe is versioned with the code that it builds.
Kernel unit and integration tests run through the same machinery
bootimage has built-in support for running unit and integration tests of your kernel. The mechanism is Rust's custom test frameworks feature, described in the README as the custom_tests_framework feature of Rust, with a link to the operating systems blog page explaining it. The practical effect is that a test binary for a no-std target can be packaged into a bootable image, run in the emulator and its exit status interpreted, which is what makes the test-success-exit-code and test-timeout settings meaningful rather than decorative. This is the part of the tool that goes beyond packaging. A kernel with no way to run its own tests in an emulator is a kernel where the test suite only runs on real hardware or not at all. The harness reuses the same image-building and runner path as normal execution, so there is one code path to maintain rather than two, and the -no-reboot flag exists so a test can end the machine cleanly instead of looping.
A small dependency list is the tell that this is a build tool, not a framework
The Cargo.toml lists a deliberately modest set of dependencies. toml at 0.5.6 is there because the configuration is parsed from the manifest, wait-timeout at 0.2.0 is how the test runner enforces a timeout, llvm-tools at 0.1.1 is for the LLVM toolchain utilities, locate-cargo-manifest finds the manifest path, json at 0.12.4 handles the metadata cargo exposes, anyhow and thiserror provide error handling, and cargo_metadata at 0.9.1 reads the package metadata the configuration lives in. There is no UI, no runtime framework and no async machinery, which is what you would expect from something whose job ends when the image file exists on disk. The release metadata in the manifest is also automated: a pre-release-replacements rule rewrites the Unreleased heading in Changelog.md with a version and date on release, and the release commit message is templated as Release version {{version}}. The repository ships both LICENSE-APACHE and LICENSE-MIT files, and the Changelog.md that the release process rewrites.
Where bootimage is the wrong tool
Two boundaries stand out. The first is that bootimage is built around the rust-osdev bootloader crate. It builds the specified bootloader together with the kernel, so if your project uses a different boot chain, a vendor bootloader or a custom boot protocol, the tool does not help, and at best you are fighting it. The second is the target assumption. The runner configuration keys on cfg(target_os = "none"), which is the bare-metal target, and the whole flow presumes a kernel that has been cross-compiled for a custom target specification file. If you are at the stage of writing hello world in a hosted Rust program, this does nothing for you. A third, softer limit is that QEMU is baked into the default run command, so a project that needs a different emulator or a physical board has to override run-command entirely, at which point the tool is contributing the image build and the test harness rather than the run step.
Dual licensed, small in scope, and last pushed in 2026
The licence arrangement is the standard Rust dual licence. The README says the project is licensed under either the Apache License, Version 2.0 or the MIT license, at your option, and that unless you explicitly state otherwise, any contribution intentionally submitted for inclusion shall be dual licensed as above without any additional terms or conditions. The Cargo manifest records the licence as MIT/Apache-2.0, which is the conventional shorthand for that. In practice this is about as permissive as a Rust project can be, and it matters for a tool that sits in everyone's build path. The repository is not archived, and the last push was on 2026-08-03, with no GitHub releases published, so the version to track is the one Cargo resolves rather than a tagged artefact. The tree also carries an example-kernels directory and an example-kernel-built-in-target directory, which are the fastest way to see the whole flow working before you point it at your own kernel.
Editorial conclusion
bootimage suits a Rust OS developer who already has a kernel target and wants the bootloader, the image build and the test harness handled by the standard cargo workflow rather than a pile of shell scripts. It does not suit someone starting from nothing, since it assumes a bare-metal target and a working kernel build, and it does not suit a project that needs a different boot chain, because it is built around the rust-osdev bootloader crate. Before you adopt it, check the version pairing, since bootimage 0.7.0 and later require bootloader 0.5.1 or newer while earlier bootloader versions pair with bootimage 0.6.6, and decide how your CI will supply the QEMU invocation, because the run command is configured per project rather than per machine.
Frequently asked questions
How do I install bootimage?
With cargo install bootimage, which installs it as a cargo subcommand. Your kernel's Cargo.toml then needs a dependency on the bootloader crate, for example bootloader = "0.9.8".
Which bootloader version does bootimage need?
At least bootloader version 0.5.1 is required since bootimage 0.7.0. For earlier bootloader versions, use bootimage 0.6.6. The current manifest lists bootimage at version 0.10.5.
Can bootimage run my kernel's tests?
Yes. It has built-in support for running unit and integration tests of your kernel, using the custom_tests_framework feature of Rust. The run command, extra arguments, a success exit code, a timeout and the -no-reboot flag are all configurable.
Where is bootimage configured?
In a [package.metadata.bootimage] table in your kernel's Cargo.toml. It covers the build command, the run command with its {} placeholder for the image path, extra run and test arguments, the test success exit code, the test timeout and whether -no-reboot is passed.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rust-osdev-bootimage)Was this useful?