Skip to content

Comparison & limitations

If you can rebuild your service as a static binary (CGO_ENABLED=0, musl), do that. You need no packer. Scratchsmith is built for the binaries that static linking cannot help:

  • Closed-source or vendor binaries you cannot recompile.
  • glibc binaries that rely on NSS, dlopen, or locale behavior. Static glibc breaks these quietly, often only in production.
  • Anything where "just rebuild it static" is not on the table.

That is the wedge, and it is not a fence. Scratchsmith already packs a static binary too, because the resolver detects it and stages the single file. When you want an SBOM, a hardening report, a non-root image, and no Dockerfile, it is equally handy. You hand-write no multi-stage build. It is purpose-built for the hard case and useful for the easy one.

How Scratchsmith compares

Every tool here builds smaller and safer images. They differ mainly in what you feed them.

Input No Dockerfile Prebuilt dynamic glibc binaries Built-in SBOM + hardening Daemonless build
Scratchsmith a prebuilt dynamic ELF binary ✅ ✅ (the whole point) ✅ SBOM + ELF hardening lint ✅ --push / --oci-archive (the default sink uses docker load)
Static + FROM scratch source you can static-link ✅ (COPY) ❌ must be static ❌ ✅
Docker multi-stage a Dockerfile + source ❌ ✅ (you hand-craft it) ❌ ❌ needs daemon / BuildKit
apko apk packages ✅ ❌ needs apk packaging ✅ ✅
ko Go source ✅ ❌ Go only partial ✅
slim an already-built image ✅ ✅ (minifies) security profiles ❌ needs a built image first

Scratchsmith owns the one input the others do not serve: an arbitrary prebuilt dynamic binary.

Snapshot as of 2026-08. The other tools evolve, so read their own docs for current behavior. Only Scratchsmith's row is kept current here.

Limitations

  • glibc / host-arch only (for now). Dynamic musl/Alpine binaries are detected by their interpreter and rejected with a clear error before anything is staged. You never get a silently broken image. This is a v1 scope choice, not a permanent boundary: musl uses a different loader model and has no NSS, so it needs its own resolver backend (a possible future addition). (A static musl binary is a single self-contained file and packs fine today.) No cross-arch resolution yet.
  • dlopen is best-effort. Libraries loaded at runtime via dlopen are invisible to static analysis. Scratchsmith warns about a dlopen reference in the binary's dynamic symbol table, and it lets you force-stage those libraries with --include <lib>. It is not a blanket "any binary just works" guarantee.
  • Loading into a container engine is the default sink. The default hands the image to a local engine for convenience: docker by default, or --runtime podman|nerdctl for those. You can also go daemon-free entirely with --push <ref> (straight to a registry), --oci-archive <file> (an OCI archive), or --no-build --output (a rootfs).
  • Signing the image pack produces needs --push. pack --push --sign cosign-signs the pushed image by digest (keyless). --sbom --sign attaches the SBOM as a signed attestation. Both apply only to the registry-push sink, because cosign signs a registry image. Release artifacts are separately cosign-signed with SLSA provenance (Verifying releases).