Convert OCI container images to ext4 rootfs filesystem images.
- Local conversion — read from an OCI Image Layout directory on disk
- Docker cache paths — convert directly from Docker
overlay2chain-id directories - Remote pull — pull images directly from container registries (Docker Hub, GCR, etc.)
- Multi-arch support — resolve platform-specific manifests from image indexes
- Layer handling — apply tar layers in order with full OCI whiteout support (
.wh.*deletes,.wh..wh..opqopaque) - Compression — gzip, zstd, and uncompressed layers
- Auth — automatic credential resolution from Docker config
# From a local OCI layout directory
oci2rootfs ./oci-dir --output rootfs.ext4
# From a remote registry
oci2rootfs nginx:1.21 --output rootfs.ext4
# Custom size and platform
oci2rootfs ubuntu:22.04 --output rootfs.ext4 --size 1G --platform linux/arm64
# Insecure (HTTP) registry
oci2rootfs localhost:5000/myapp:latest --output rootfs.ext4 --insecure| Flag | Default | Description |
|---|---|---|
--output, -o |
(required) | Output ext4 image path |
--size |
512M |
Image size (128M, 1G, etc.) |
--platform |
linux/amd64 |
Target platform |
--insecure |
false |
Allow HTTP registry connections |
--label |
(none) | Volume label written to the superblock (≤16 bytes) |
--uuid |
random | Filesystem UUID written to the superblock |
Set RUST_LOG=info (or debug, trace) to see per-layer progress and
timing on stderr — the CLI installs a tracing-subscriber fmt layer
wired to RUST_LOG via env-filter.
use oci2rootfs::{autodetect, Converter, OciLayoutSource, Platform};
// Local OCI layout
Converter::new("rootfs.ext4")
.size(1024 * 1024 * 1024)
.convert(
OciLayoutSource::open("./oci-dir")?
.platform(Platform::new("linux", "amd64")),
)?;
// Local auto-detect (OCI layout or overlay2 path)
Converter::new("rootfs.ext4").convert(autodetect("./some-path")?)?;
// Remote registry (default feature: `remote`)
# #[cfg(feature = "remote")]
# async fn example() -> oci2rootfs::Result<()> {
use oci2rootfs::RemoteRef;
let source = RemoteRef::new("nginx:latest")
.platform(Platform::default())
.fetch()
.await?;
// Remote fetches also expose the resolved manifest digest.
println!(
"manifest: {}",
source
.manifest_digest()
.expect("remote sources always resolve to a manifest")
);
// Image config (entrypoint/cmd/env/...) is available on the source.
if let Some(cfg) = source.config() {
if let Some(container) = &cfg.config {
println!("entrypoint: {:?}", container.entrypoint);
}
}
Converter::new("rootfs.ext4").convert(source)?;
# Ok(())
# }| Property | Value | Configurable |
|---|---|---|
| Block size | 4 KiB | No (arcbox-ext4 fixed) |
| Journal | none (effectively mkfs.ext4 -O ^has_journal) |
No |
| Feature flags | `SPARSE_SUPER2 | EXT_ATTR` |
| Volume label | empty | Ext4Options::label / --label |
| UUID | random v4 | Ext4Options::uuid / --uuid |
ImageConfig |
exposed via ImageSource::config() for OCI layout and remote pulls; None for overlay2 sources (Docker keeps the config outside the layer tree) |
— |
| Manifest digest | exposed via ImageSource::manifest_digest() for OCI layout and remote pulls; None for overlay2 sources |
— |
| Feature | Default | Pulls in |
|---|---|---|
remote |
yes | containerregistry-registry, containerregistry-auth, tokio, reqwest, TLS stack |
Add default-features = false on the library dependency to drop the remote
pull stack when you only need OCI layout and Docker overlay2 support:
[dependencies]
oci2rootfs = { version = "0.2", default-features = false }- Remote pull buffers every layer blob in memory before applying. For
multi-gigabyte images this requires proportional heap; prefer spooling to an
OCI layout on disk for large images (e.g. via
skopeo copy docker://<ref> oci:./layout:latest, thenOciLayoutSource::open). - Layer downloads are serial. Parallel fetches could reduce wall time for images with many small layers but would complicate progress reporting.
- No device-node support.
mknod-created entries in tar layers (char/ block/FIFO) are skipped silently; overlay2.wh.<name>files cover deletion. - Block size and inode ratio are not configurable.
arcbox-ext4hard-codes 4 KiB blocks and computes inode counts internally.
cargo build --releasecrates/
├── oci2rootfs/ # Library
│ └── src/
│ ├── convert.rs # High-level Converter API
│ ├── error.rs # Error types
│ ├── ext4.rs # ext4 image writer (arcbox-ext4)
│ ├── layer.rs # Tar layer application with whiteout support
│ ├── oci.rs # OCI Image Layout resolution
│ ├── ext4_options.rs # UUID/label passed at format time
│ ├── overlay2/ # Docker overlay2 resolution and apply
│ ├── path.rs # Shared path sanitation + whiteout parsing
│ ├── pull.rs # Remote registry pull (feature = "remote")
│ └── tar_source.rs # Shared tar-layer source plumbing
└── oci2rootfs-cli/ # CLI binary
└── src/
└── main.rs
Licensed under either of
at your option.