Everything your app needs. Nothing else.
elfpak is an alternative to magicpak. It turns a compiled binary and the filesystem it was built against into a deterministic, minimal rootfs. You can use this rootfs to build a small FROM scratch container or OCI image, with no unnecessary files. This gives you more secure container images, because only your application and its system dependencies remain exposed to possible security vulnerabilities.
- very small artefacts
- only the files strictly needed to run your app
- much more secure container images
flowchart TB
Build["cargo build"] --> Binary["ELF binary"]
Binary --> Bundle["elfpak bundle"]
Bundle --> Rootfs["minimal rootfs directory"]
Bundle --> Tar["deterministic rootfs tar"]
Bundle --> OCI["OCI image layout or archive"]
Bundle --> Manifest["manifest"]
Rootfs --> Scratch["FROM scratch"]
Tar --> Scratch
OCI --> Registry["registry or daemonless runtime"]
Rootfs --> Verify["elfpak verify"]
Manifest --> Verify
In detail: elfpak reads a Linux ELF executable. It resolves the runtime closure the same way the glibc loader does — through PT_INTERP, recursive DT_NEEDED, DT_RPATH/DT_RUNPATH, $ORIGIN expansion, ld.so.cache, and ld.so.conf. Then it copies exactly that closure into the bundle, with its original paths and symlinks. elfpak does not execute the binary, guess from filenames, or use tracing to find files.
elfpak supports x86_64 and aarch64, including cross-architecture packaging from a foreign sysroot. It bundles statically linked and musl-linked binaries through generic ELF parsing. musl-specific loader behavior is out of scope.
cargo vendor, but for an executable's Linux runtime.
# syntax=docker/dockerfile:1
FROM ghcr.io/asaaki/elfpak:latest AS elfpak
FROM rust:1.98.0-slim-trixie AS build
WORKDIR /src
COPY --from=elfpak /elfpak /usr/local/bin/elfpak
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo build --release --locked && \
cp target/release/my-server /my-server
RUN elfpak bundle /my-server \
--output /rootfs \
--install /app/server \
--preset web \
--user 65532:65532
FROM scratch
COPY --from=build /rootfs /
USER 65532:65532
WORKDIR /app
ENTRYPOINT ["/app/server"]Do not use latest. Pick a specific image tag instead (see the registry). You can also pin the image by its digest.
The resulting image contains the application, its ELF closure, and the files the runtime policy asked for.
To use elfpak outside of a Docker build, install it with cargo binstall.
cargo binstall elfpakInstall the Cargo adapter to package binaries from a Rust project.
cargo binstall cargo-elfpak
cargo elfpak bundle --release \
--output rootfs \
--install /app/server \
--preset webcargo-elfpak asks Cargo to build the selected binaries. Cargo reuses a binary when it is fresh. Cargo rebuilds a binary when a tracked input changed. cargo-elfpak passes the exact executable paths that Cargo reports to the normal elfpak bundle step.
Use -p <package> in an ambiguous workspace. Use --bin <name> when Cargo cannot infer a default binary for the package.
For a multi-binary project, you have three options: select a subset with -p <package> --bins server,migrate, select every binary in one package with -p <package> --all-bins, or select every binary in the workspace with --all. Use --install-dir to keep each binary's name under one directory.
cargo elfpak bundle --release \
--all \
--output rootfs \
--install-dir /app \
--preset webelfpak inspect <binary> analyze and print the runtime closure, copying nothing
elfpak bundle <binary>... build a minimal rootfs plus a manifest
elfpak verify <manifest> check a materialized rootfs against its manifest
bundle writes any combination of these outputs from the same plan: a directory (--output), a deterministic rootfs tar (--tar, for ADD rootfs.tar /), an OCI image layout (--oci-layout), and an OCI layout archive (--oci-archive).
Use this to build a runnable image without Docker or a container daemon.
cargo elfpak bundle --release \
--bin server \
--oci-archive dist/server.oci.tar \
--install /app/server \
--image-tag ci \
--entrypoint /app/server
skopeo copy \
oci-archive:$PWD/dist/server.oci.tar:ci \
docker://ghcr.io/example/server:latestFor the directory form, use --oci-layout dist/server.oci and oci:$PWD/dist/server.oci:ci. The archive is a tar of an OCI layout. Do not extract it at / like a rootfs tar. See DOCUMENTATION.md for Skopeo, ORAS, Podman, nerdctl, Crane, and GHCR CI examples.
There are two presets. minimal is the ELF closure alone. web adds CA certificates, /tmp, passwd/group, and nsswitch.conf. You can also switch on each feature by itself. An optional elfpak.toml file can supply defaults.
A service packaged with --preset web can do DNS lookups and outbound HTTPS without CA-specific code in the application. The system trust store comes with the bundle.
- Loader semantics, not filename matching.
elfpakfollowsPT_INTERP, recursiveDT_NEEDED,DT_RPATHinheritance versusDT_RUNPATH,$ORIGIN/$LIB/$PLATFORM,ld.so.cache, andld.so.conf. It deliberately excludes unsafe CPU-specific glibc-hwcaps variants and validates the architecture of every candidate. - Original paths and symlinks stay intact.
libfoo.so.1 -> libfoo.so.1.4.2stays a symlink.elfpakdoes not relocate files into a private directory with a compensatingLD_LIBRARY_PATH. When a library sits outside the directories the loader searches, the bundle gets a generated/etc/ld.so.cacheinstead. This cache is real and comes from the plan, becauseelfpaknever runsldconfig. - Every file has a recorded reason. The manifest beside the rootfs names each included file, the reason for it, and the policy used to build it.
elfpak verifychecks the manifest again.--strictalso rejects a file added afterward or a file with changed permissions. - An allow-list turns dependencies into a contract. A new native dependency fails the build instead of growing the image without notice.
- Cross-architecture.
--rootabstracts the source filesystem. This lets an x86_64elfpakpackage an aarch64 application from an aarch64 sysroot.
elfpak bundle does not execute the target, call ldd or ldconfig, run shell commands, contact the network, or invoke Docker. OCI production is also daemonless. elfpak treats the source filesystem as read-only and writes only to the requested artifact destinations and their temporary siblings.
Tar output is deterministic for the same binaries, source root, configuration, and elfpak version. Set SOURCE_DATE_EPOCH to pin timestamps for planned files and directories. Tar is the portable, byte-reproducible output.
elfpak stages directory, tar, OCI, and manifest outputs beside their destinations and publishes them only when complete. As a result, a failed build leaves the previous artifact intact instead of exposing partial output. OCI layouts use one uncompressed, deterministic layer and content-addressed config and manifest blobs.
DOCUMENTATION.md covers the full CLI, runtime policy, configuration file, dependency policy, manifest format, resolver behavior, cross-architecture packaging, and the test suite.
just check # fmt, clippy -D warnings, and the whole test suite
just test # unit, integration and loader-oracle tests
just smoke # Docker smoke tests (see DOCUMENTATION.md)
just smoke --fresh # ... with nothing reused from a previous run
just oci-smoke # Skopeo + Podman interoperability, no Docker
cargo run -p cargo-elfpak -- bundle --help
docker buildx build --platform linux/amd64,linux/arm64 -t elfpak:local --load .The design takes ideas from TigerStyle: safety first, bounded work, explicit invariants, deterministic output, and performance that does not cost readable Rust. STYLE.md records the project's adaptation. It does not impose mechanical line-count rules.
The distribution image is multi-platform and cross-compiled. Building every architecture never needs emulation.
elfpak implements rootfs, deterministic tar, and single-platform OCI image outputs for x86_64 and aarch64. It also has loader-oracle tests against real glibc and parser fuzzing. Future work includes runtime tracing (elfpak trace), multi-platform OCI index assembly, direct registry push, and SBOM generation.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.