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

meta-balena: the Yocto layers behind a balenaOS image

A collection of Yocto layers used to build balenaOS images

988 stars131 forksBitBakeLicense varies

At a glance

What is it?
meta-balena is the collection of Yocto layers that build balenaOS images, with one common layer shared across platforms and a set of layers pinned to specific Yocto releases. It suits someone building a device image rather than deploying containers, and the per-board version scheme is the part that needs care.
Who is it for?
Adopt meta-balena when you are producing a balenaOS image for a device, because it is the layer set that defines the userland, the bootloader behaviour and the host OS, and it carries the same configuration the vendor ships. Do not adopt it to run containers on a device you already have, because this repository builds an image rather than deploying to one.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly BitBake, 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

Layers, and the Yocto versions they follow

meta-balena is described as a collection of Yocto layers used to build balenaOS images, and the repository enables building balenaOS for various devices.

The layer structure has three parts. meta-balena-common is the layer with the common recipes for all supported platforms. The meta-balena- layers contain recipes specific to Yocto versions. And the other files are the README, COPYING and the rest.

The top level directory listing shows how many Yocto generations are carried at once. There is meta-balena-dunfell, meta-balena-honister, meta-balena-kirkstone, meta-balena-rust, meta-balena-scarthgap, meta-balena-thud, meta-balena-warrior and meta-balena-wrynose, alongside meta-resin-common and the older meta-resin-pyro, meta-resin-rocko and meta-resin-sumo.

Carrying a layer for each Yocto branch in one repository is the design decision that explains most of what follows. A Yocto release has a fixed bitbake version and a fixed set of package versions, so supporting a device across a Yocto upgrade means keeping a layer per branch rather than a single layer with conditionals. The cost is that a change to a common recipe has to be applied to every branch, and the benefit is that a board can move to a newer Yocto without a rewrite.

The primary language is BitBake, which is the recipe language Yocto uses, and the repository carries a repo.yml, which is the file the Yocto repo tooling uses to fetch the layers that are not in this repository, plus a hostapp.yml, a tests/ directory, a.versionbot/ directory and a balena-docs/ directory.

The licence is reported as unknown, though the repository carries a COPYING.Apache-2.0 file at the top level, and the homepage is balena.io/os. The last push was on 2026-10-19, with v9.0.0 published on 2026-10-16 and v8.0.15 the day before.

Two version numbers, and a rev suffix that tracks board revisions

The versioning section is the part of the README that most people building an image have to get right, because there are two versions and they move at different rates.

The meta-balena version is kept in the DISTRO_VERSION variable. The balena-<board> version is kept in a file called VERSION located in the root of the balena-<board> repository, and read in the build as the variable HOSTOS_VERSION. The host OS version is defined as the balena-<board> version, and that is the version used as HOSTOS_VERSION.

The meta-balena version is in semver format, three numbers separated by a dot, and the patch number can carry a beta label, for example 1.2.3, 1.2.3-beta1 or 2.0.0-beta1.

The board version then derives from it. The first balena-<board> release based on a specific meta-balena release X.Y.Z will be X.Y.Z, the same as the meta-balena version. Subsequent board releases append a rev label, so a meta-balena 1.2.3 can go through three board revisions, with 1.2.3 as the initial revision and 1.2.3+revN for the subsequent ones.

And when the meta-balena version is updated in the board, the version resets to the meta-balena version, so 1.2.3+rev4 becomes 1.2.4.

That last rule is the one that catches people. The rev counter is not a permanent board revision number, it is a counter within one meta-balena release, and it resets when the base moves. So a board that has been through four revisions is not on revision four of a stable line, it is on the first revision of a new base.

Build flags, local.conf and the splash logo

The build flags section is short and every flag is a decision you might need to make.

Before bitbake-ing with meta-balena support, a few flags can be changed in conf/local.conf from the build directory, and the README is clear that editing local.conf is to be done after source-ing.

The network manager is the first. By default balena uses NetworkManager on the host OS to provide connectivity, and if you want to change and use other providers you list your packages using NETWORK_MANAGER_PACKAGES, with the example being a pair of package names. The Docker storage driver is the second. By default the build system sets everything needed for Docker to use the aufs storage driver, and this can be changed by defining BALENA_STORAGE in local.conf, which supports aufs and overlay2.

The splash logo is the third and it is a file rather than a flag. All of the initial images are configured to produce a balena logo at boot, shutdown or reboot, and the README encourages any user to replace that logo with their own. You replace the splash/balena-logo.png file in the first partition of the images, the boot partition. The filename is not arbitrary: plymouth expects the image to be named balena-logo.png, and in older releases the file was called resin-logo.png, which is a small detail that will break a script that assumes the old name.

The fourth is a development switch. To configure a development build that disables quiet boot and allows bootloader shell access, you add to the build's local.conf:

code
OS_DEVELOPMENT = "1"

and the README states this is a development only setting and that no OS_DEVELOPMENT configured images are deployed. That last sentence is the safety property, since a shell on the bootloader of a device that ships to a customer is not something you want enabled by accident.

How the boot time clock gets set, in five steps

The time synchronisation section is the most detailed part of the README, and it reads like a specification of a real system rather than a feature list.

At boot, time is set and synchronised in this order: build time, previous boot system time, RTC time when available, HTTPs time, and network time.

Build time comes first. The image build time timestamp is stored in /etc/timestamp and generated by the build system when the image is generated.

Then the previous boot system time. The system checks whether the previous boot system time was stored in persistent storage and uses it to correct the time. Time is stored in persistent storage on an hourly timer and on system reboot or shutdown, and logging starts after the last boot system time is set. That last detail is the point of the whole step, since a device that has been off for a month should come up with a plausible clock rather than 1970, and a device with a working RTC should not be thrown back to the build date.

RTC time comes third. When an RTC is available at /dev/rtc, a timeinit-rtc service is started that updates the system clock using the value read from the RTC, and if there is no RTC the service does nothing.

HTTPs time comes fourth. After a network connectivity event, a time synchronisation service called timesync-https corrects the time from HTTP headers timestamps, assuming the correct system time was not already set from the RTC. The stated reason is that this guarantees the time is broadly correct and certificate expiration checks will not fail. Other network services are held until this synchronisation happens, and the connectivity URL comes from the connectivity section of config.json. Setting that URI to 'null' disables the HTTPs sync and the connectivity checks with it.

Network time is last and permanent. The chronyd service manages the time afterwards using NTP, configured to synchronise every four hours or so to save bandwidth. If the NTP servers become unreachable the service continuously retries, and if the time becomes unsynchronised the NTP client service is restarted to correct failures.

SSH on 22222, and a bootloader that can roll back

Two more runtime details round out the picture.

The OS runs SSH, openSSH, on port 22222. The README explains the reason in a way that matters for a device: running this service takes advantage of the systemd socket activation feature, so the SSH daemon only runs when there is an SSH connection to the device, saving idle resources. To connect you use the device's IP when it is known, or you resolve the hostname over mDNS, because the hostname is advertised over the network using an avahi service. The latter needs client configuration, and the README points at the Arch Wiki entry on Avahi hostname resolution.

The bootloader is the other piece. The bootloader needs to select the active root filesystem, load and launch the Linux kernel, and it also manages boot counts and rollbacks. BalenaOS supports several bootloaders across the supported device line-up, and the preferred one for new kexec capable devices is the balena bootloader.

What makes the balena bootloader interesting is described in three points. It is a minimal Linux kernel that contains all of balena's logic, such as rollback support, in the initramfs, so that the logic does not have to be replicated across different bootloaders. It fetches the final kernel from the active root partition and kexecs into it. And it is a requirement for secure boot enabled platforms, because it needs to mount and decrypt root filesystems to launch the final kernel, and decrypting disks is not usually supported on standard bootloaders.

That last point is a hard constraint rather than a preference. On a device with secure boot enabled, the choice of bootloader is not yours.

Editorial conclusion

Adopt meta-balena when you are producing a balenaOS image for a device, because it is the layer set that defines the userland, the bootloader behaviour and the host OS, and it carries the same configuration the vendor ships. Do not adopt it to run containers on a device you already have, because this repository builds an image rather than deploying to one. Before you build, read the versioning section, since the meta-balena version and the per-board version are separate numbers with a rev suffix scheme, and note the build host requirements, which are the Yocto build host packages plus docker and jq.

Frequently asked questions

What is meta-balena and how is it organised?

It is a collection of Yocto layers used to build balenaOS images for various devices. meta-balena-common holds the common recipes for all supported platforms, the meta-balena- layers hold recipes specific to individual Yocto versions, and the top level holds the README, COPYING and the other files. The repository carries layers for Yocto branches from dunfell through wrynose.

How are the meta-balena and board versions related?

The meta-balena version lives in the DISTRO_VERSION variable and is semver, possibly with a beta label. The balena-<board> version lives in a VERSION file at the root of the board repository and is read as HOSTOS_VERSION. The first board release based on a meta-balena X.Y.Z is X.Y.Z, later board revisions append a rev suffix such as 1.2.3+rev2, and the rev counter resets when the meta-balena version moves.

Which build flags can I change in local.conf?

NETWORK_MANAGER_PACKAGES replaces the default NetworkManager with other connectivity providers, BALENA_STORAGE selects the Docker storage driver between aufs and overlay2, and OS_DEVELOPMENT set to "1" produces a development build with quiet boot disabled and bootloader shell access. The splash logo is changed by replacing splash/balena-logo.png in the boot partition.

How does balenaOS set the clock at boot?

In order: the image build time stored in /etc/timestamp, the previous boot system time from persistent storage, the RTC when one is available at /dev/rtc, an HTTPs time sync from HTTP header timestamps after a connectivity event, and then chronyd with NTP every four hours or so for the long term. The HTTPs sync can be disabled by setting the connectivity check URI to 'null' in config.json.

Official sources

  1. balena-os/meta-balena on GitHub
  2. Issues
  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-meta-balena.svg)](https://hysenlabs.com/projects/balena-os-meta-balena)

Was this useful?