Self-hosted service
balena-os/balena-engine avatar
balena-os/balena-engine

balenaEngine: a Moby-based container engine built for IoT fleets

Moby-based Container Engine for Embedded, IoT, and Edge uses

744 stars69 forksGoApache-2.0

At a glance

What is it?
balenaEngine is a Go port of the Moby project aimed at embedded, IoT and edge devices, offering container deltas, atomic image pulls and a single small binary instead of the full Docker CE feature set. It runs Docker containers unchanged as long as you do not rely on Swarm, plugin support, overlay networking drivers, cloud logging drivers or BuildKit.
Who is it for?
balenaEngine fits teams shipping containerised software to embedded fleets where bandwidth, flash writes and power loss are the binding constraints and where the full Docker CE feature set would be dead weight, and it fits fleet operators already committed to Docker-format images. It does not fit a workload that needs Swarm orchestration, plugin support, overlay networking drivers, cloud logging drivers or BuildKit, because balenaEngine leaves those out.
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 received new commits within the last day.
What is it written in?
Mainly Go, 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

A Moby fork aimed at devices, not servers

balenaEngine describes itself as a moby-based container engine for IoT, a fork of Docker's Moby project with a narrower feature set and a different set of engineering priorities. The homepage points at balena.io, the company behind it, and the default branch is release/v25.0.

The lineage explains the design. The README says that since 2013, when the team first ported Docker to ARMv6 and the Raspberry Pi, the balena team has been working in and around the Docker codebase. The stated reason for building a separate engine is that the team had seen IoT devices used in production for tens of millions of hours and became familiar with what the embedded world actually needs.

That history gives the project a clear audience: fleet operators and device manufacturers who run Docker-format containers on constrained hardware. The value proposition is compatibility with an existing container ecosystem plus a set of changes aimed at small devices. The README is explicit that, unless you depend on a Docker feature that balenaEngine omits, using it should be a drop-in replacement.

The language is Go, and the licence is Apache License 2.0 with the full text in a LICENSE file and a NOTICE file beside it, which matters for vendors shipping it in a device image.

One small binary and a wide architecture range

The headline claim is size. The README highlights describe the engine as 3.5x smaller than Docker CE and packaged as a single binary. A single binary is the part that matters operationally: on a device with limited flash and a fragmented filesystem, one file is easier to ship, verify and replace than a daemon plus plugins plus configuration directories.

Multi-architecture support is the other headline. The engine is described as available for a wide variety of chipset architectures, supporting everything from tiny IoT devices to large industrial gateways. The repository's own build setup backs that up: the Dockerfile pins Go 1.24.6, the trixie Debian base and the xx cross-compilation helper at 1.6.1, and the presence of Dockerfile.windows and Dockerfile.simple shows that more than one target shape is built.

The build arguments are pinned explicitly, which is what makes reproducible images possible.

dockerfile
ARG GO_VERSION=1.24.6
ARG BASE_DEBIAN_DISTRO="trixie"
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
ARG XX_VERSION=1.6.1

One inconsistency is worth flagging for anyone quoting these numbers. The highlights section says 3.5x smaller than Docker CE, while the Motivation section says the project has 3x smaller binaries. Both figures come from the same README, and neither is tied to a specific build configuration, so treat them as project claims rather than measurements you can rely on when planning a device image.

Container deltas cut update bandwidth by an order of magnitude

The feature that separates balenaEngine from a plain Moby build is the container delta. Instead of pulling whole layers, the engine can apply binary diffs between image versions, and the README puts the saving at 10 to 70 times smaller than pulling layers, with the motivation section describing it as 10 to 70 times more efficient bandwidth usage.

For a fleet update this is the difference between an operation that fits on a metered cellular link and one that does not. A device that receives a small delta instead of a full layer set also writes less to its flash storage, which matters because flash wear is a hard limit on device lifetime.

The related storage behaviour is described as minimal wear-and-tear: layers are extracted as they arrive, which the README says prevents excessive writing to disk and protects storage from eventual corruption.

So the update path is designed around two constraints that do not show up in a cloud deployment: the pipe is small and the disk is not replaceable. The delta approach addresses the first, the extract-on-arrival behaviour addresses the second, and both are listed as headline features rather than as tuning options.

Atomic pulls and conservative memory use for devices that lose power

Failure resistance is the third area the README emphasises. It describes failure-resistant pulls as atomic and durable, intended to defend against partial container pulls when power fails. An atomic pull means the device either has the new image or keeps the old one, instead of being left with a half-written layer that bricks the service on next boot.

Memory behaviour is treated with the same seriousness. The engine is described as using RAM and storage more conservatively, and as preventing page cache thrashing during image pull so the application runs undisturbed in low-memory situations. On a device with tens or hundreds of megabytes of RAM, an image pull that evicts everything else is a visible outage, so this is a real feature rather than a nicety.

The result is an engine whose differences from Docker CE are concentrated in the update path and the resource envelope, and whose interface stays the same. That is a coherent position: keep the container format and the CLI behaviour, change how bytes arrive and how the disk and RAM are treated while they arrive.

Swarm, plugins, overlay drivers and BuildKit are left out on purpose

The README has a section on transitioning from Docker CE, and it is the most useful page in the repository for anyone evaluating the project. Features the team saw as most needed in cloud deployments, and therefore not worth carrying in a lightweight IoT engine, were excluded.

The list is: Docker Swarm, cloud logging drivers, plugin support, overlay networking drivers, non-boltdb discovery backends such as consul, zookeeper and etcd, and Buildkit. BuildKit is the only one with an escape hatch, since support can be enabled using a build tag.

Every item on that list corresponds to a capability that is genuinely cloud-shaped. Swarm is cluster orchestration across machines, plugin support extends the daemon's own surface, and the non-boltdb discovery backends exist for services that need to find each other in a large cluster. Dropping them is what keeps the binary small, so the omissions are the price of the footprint claim rather than an oversight.

The practical reading is that the excluded list defines your migration risk. If your images rely on a swarm-style deploy workflow, custom logging drivers or the overlay network driver, balenaEngine is the wrong engine, and no amount of tuning changes that.

The repository is a full Moby tree with cross-compilation tooling

The file listing is essentially the Moby project layout, which is what a maintained fork looks like: api/, builder/, cli/, client/, cmd/, container/, daemon/, distribution/, image/, layer/, libcontainerd/, libnetwork/, oci/, pkg/, plugin/, registry/, restartmanager/ and runconfig/, alongside integration-cli/, integration/, hack/, man/ and docs/. A docker-bake.hcl file sits at the root for multi-target builds, and the top-level Dockerfile, Dockerfile.simple and Dockerfile.windows cover different build shapes.

The Makefile shows how the build is driven, with docker and buildx as overridable variables.

bash
DOCKER ?= docker
BUILDX ?= $(DOCKER) buildx

It also detects the graph driver of the current daemon and exports it, which is the mechanism behind the graph driver priority override described in the comments.

bash
DOCKER_GRAPHDRIVER := $(if $(DOCKER_GRAPHDRIVER),$(DOCKER_GRAPHDRIVER),$(shell docker info -f '{{ .Driver }}' 2>&1))

A long list of DOCKER_* environment variables is passed through to the build scripts so that things like build flags and package selection can be overridden from the command line. The repository also carries DEVELOPMENT.md, TESTING.md, VENDORING.md, ROADMAP.md, SECURITY.md and CONTRIBUTING.md, and a .golangci.yml for linting, so contributions go through a documented path.

A moving default branch and published tags that trail behind it

The branch layout says the project ships on a per-release branch model. The default branch is release/v25.0, and the last push was on 2026-10-14, so the branch is being worked on. The published releases listed for the repository, however, are on the older v20.10 line: v20.10.43 from 2024-02-06, v20.10.42 from 2023-12-19 and v20.10.41 from 2023-12-01.

Read that carefully before assuming a version number maps to a release. The tags on the v20.10 line are history, while the default branch is where v25.0 work sits, and a deployment pinned to a release tag is therefore pinned to the older line rather than to the current default branch.

The licence position is Apache-2.0, with the LICENSE and NOTICE files at the root. For a vendor embedding the engine in a device image, that means you keep the notice and the licence text, and Apache 2.0 carries the usual patent grant, which is one of the reasons it is a common choice for infrastructure components.

The upgrade cost of the branch model is that a fleet operator has to decide deliberately which line to run, and the Docker-side compatibility is stated as a property of the engine rather than as a compatibility matrix, so the excluded feature list is the contract you should test against.

Docker CE on the same hardware, and what that choice costs you

The honest alternative is to keep Docker CE on the device. The difference in approach is not philosophical: it is that balenaEngine keeps the Docker interface and the container format while dropping the features that a single device has no use for, and adds a delta-based update path plus extract-on-arrival layer handling. Docker CE gives you the features balenaEngine leaves out, at the cost of the footprint and the update behaviour that motivate this project in the first place.

A second option is to avoid a container engine on the device entirely and run the application directly, which removes the daemon from the boot path and the update machinery with it, at the cost of per-device application distribution rather than per-fleet image distribution.

Whichever path you pick, the deciding questions are concrete. Does your workload depend on anything on the exclusion list, in particular Swarm, plugin support or the overlay driver. Is bandwidth on your device link the binding constraint, in which case the delta mechanism is the main reason to prefer this fork. And is the flash write budget tight enough that the wear-and-tear behaviour changes the device's service life. Answer those and the choice between balenaEngine and Docker CE on the same hardware is not close.

Editorial conclusion

balenaEngine fits teams shipping containerised software to embedded fleets where bandwidth, flash writes and power loss are the binding constraints and where the full Docker CE feature set would be dead weight, and it fits fleet operators already committed to Docker-format images. It does not fit a workload that needs Swarm orchestration, plugin support, overlay networking drivers, cloud logging drivers or BuildKit, because balenaEngine leaves those out. Before adopting it, check the omitted feature list against what your images actually require, confirm your device architecture is among the built targets, and read the release notes for the branch you plan to pin, since the published tags in the repository still sit on the v20.10 line while the default branch is release/v25.0.

Frequently asked questions

What is balenaEngine and is it compatible with Docker containers?

It is a Moby-based container engine for embedded, IoT and edge use, written in Go and licensed under Apache 2.0. The README states that unless you depend on a Docker feature it omits, using balenaEngine should be a drop-in replacement.

Which Docker features does balenaEngine not support?

The excluded list is Docker Swarm, cloud logging drivers, plugin support, overlay networking drivers, non-boltdb discovery backends such as consul, zookeeper and etcd, and Buildkit. BuildKit support can be enabled using a build tag.

What are container deltas in balenaEngine and how much bandwidth do they save?

Container deltas apply binary diffs between image versions so devices receive a patch rather than whole layers. The README claims they are 10 to 70 times smaller than pulling layers, describing it as 10 to 70 times more efficient bandwidth usage.

Which hardware does balenaEngine target?

The README describes support for a wide variety of chipset architectures, from tiny IoT devices to large industrial gateways, and the project has worked on ARMv6 and Raspberry Pi support since 2013. The repository builds with Go 1.24.6 on a trixie base using the xx cross-compilation helper.

How does balenaEngine handle power loss during an image pull?

The README describes failure-resistant pulls as atomic and durable, intended to defend against partial container pulls in the event of power failure. It also extracts layers as they arrive to limit disk writes, and prevents page cache thrashing during pulls so the application is undisturbed on low-memory devices.

Official sources

  1. balena-os/balena-engine on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/balena-os-balena-engine.svg)](https://hysenlabs.com/projects/balena-os-balena-engine)

Was this useful?