Skip to main content

Reduce embedded Linux firmware size

The problem

Embedded Linux footprint matters: cellular IoT devices ship with tens of MB of flash, industrial gateways are quoted in hundreds of MB, and every megabyte is bandwidth, cost, and update window. Most container runtimes built for cloud (Docker + containerd, even k3s) sit at tens-to-hundreds of MB just for the engine — before any application code.

Pantavisor's footprint

Pantavisor itself is a single ~1 MB binary that runs as PID 1. There is:

  • No container daemon — no dockerd, no containerd. Each LXC container is a normal supervised process tree.
  • No image cache — content-addressed objects are stored once in objects/, referenced by hash from any state revision.
  • No mandatory overlay layer cache — Docker requires overlayfs and bookkeeping; Pantavisor doesn't.

A realistic minimal Pantavisor system:

ComponentApprox. size
Pantavisor (PID 1)~1 MB
LXC + supporting binaries~3–5 MB
Kernel + initramfs (varies by board)5–15 MB
Minimal BSP container (Alpine + ConnMan style)8–20 MB
Per-app container (typical)5–30 MB

A complete Raspberry Pi gateway image with BSP + connectivity + a single app commonly fits in ~50–80 MB of flash.

Why it stays small

  1. No long-running daemon. dockerd is a privileged root daemon that idles at significant RAM and disk. Pantavisor is PID 1; no separate process owns container lifecycle.
  2. Content-addressed object store. Every file (kernel image, container rootfs, config blob) is stored by its SHA256 hash. Two containers sharing the same library share the same object; two revisions sharing 99% of their content share 99% of objects.
  3. Flexible storage backends. Containers can use squashfs (read-only, compressed), dm-verity (verified read-only), raw block volumes, or overlayfs — only when you actually want it.
  4. Differential OTA. Updates ship only changed object hashes; a 200 KB config tweak doesn't push a 200 MB rootfs over cellular.
  5. Kernel and BSP as containers. Kernel + modules + firmware live inside a bsp/ container sharing the same object store — no separate system partition duplicating storage.

Practical footprint-reduction tips

  1. Use squashfs root volumes in your container recipes for a 50–70% size reduction vs a plain rootfs.
  2. Strip the BSP — disable kernel features you don't use; PANTAVISOR_FEATURES toggles what's compiled in.
  3. Share base layers across containers — use the --base flag with pvr app add to create overlay containers that diff against a shared base.
  4. Distinguish core from optional containersPVROOT_CONTAINERS_CORE (always present) vs PVROOT_CONTAINERS (optional, installed on first boot). Optional containers don't bloat the initial flash.
  5. Compress on transferpvflasher accepts .bz2, .xz, .zst, and .gz directly; ship images compressed and decompress on the device.

Trade-offs to be aware of

  • System containers are larger than Docker app containers by design — they include init and supporting userspace. The overall system is smaller, but a single Pantavisor container is usually larger than a single Docker app container.
  • Kernel-as-container adds the BSP container's overhead — this buys you OTA-able kernels; pay it knowingly.

Compared to alternatives

StackEngine footprintNotes
Docker on embedded~50–100 MB for dockerd + containerdPlus image-cache growth
balenaEngine + balenaOS~80–200 MB host OSSlimmed Docker, still daemon-based
Pantavisor + LXC~1 MB Pantavisor + small LXCNo daemon, content-addressed dedup
Buildroot aloneSmallest in class (single-digit MB)But no container runtime, OTA, or fleet

If raw minimalism is the only goal, Buildroot still wins. If you want minimal plus containers + OTA + rollback + fleet, Pantavisor is the smallest stack that delivers that combination.

Next steps