Memory prices have been climbing since 2020, first from pandemic supply chain disruption, then from AI data centers locking up silicon production. Analysts at Jefferies project a 50% price increase in Q3 2026 and another 30 to 40% in Q4, with no relief expected until 2028. For developers and integrators, that means the hardware available for a new design may no longer be the specification you planned for. When budget or availability forces a downgrade, minimizing what your system actually uses becomes the fastest path back to a workable design.
The Yocto Project is one of the strongest tools for this job, since it gives full control over what goes into a board support package, down to individual packages and kernel options. This article walks through four levers for cutting memory usage in a Yocto image:
- removing unnecessary packages (bushwhacking),
- tuning compilation flags,
- trimming the kernel configuration,
- and streamlining the init system.
A cumulative test on a core-image-sato baseline shows the actual impact of each step: kernel and init tuning produce modest but real gains, while removing a single oversized package, x11, delivers the largest reduction by far.
Bushwhacking
Sometimes the best way to save memory is to remove what you don’t need to begin with. When building an image with Yocto, the packages you want on your image require dependency packages to build. Sometimes these packages are required for runtime, and other times they are strictly for building the package. If you’re not careful, these extra packages can sneak their way on to your image and slowly creep up your memory and storage utilization.
For example, when reviewing a board support package provided to Witekio, during review it was found to be over 5GB in size. After analyzing and removing the unnecessary packages and reevaluating what is required for the device, the image shrunk down to 200MB. As an added benefit this reduced over 1000 CVEs (common vulnerability exposures). Not only did this cut down on the image size, but with that much software removed from the image, the footprint of memory utilization is significantly lowered.
Generating the Package List
So how was this accomplished? Yocto has a built-in tool that will allow you to generate the packages and tasks required to build a recipe.
bitbake -g your-image-name
This will generate two files, one that contains the task dependencies for the image, and another which contains the package dependencies pn-buildlist for the image.
We can focus on the package dependencies as that is where space will be used. Inside of the file is the list of packages that are required to build the image.
We can ignore and remove the *-native packages, as they are designed to be used by the host system and not the target system.
Then comes the time-consuming analysis of the packages deployed to the target image. With a little trial and error of trimming packages and deploying the image you will see if you needed a package or not.
An Example of Package Creep
When building scarthgap’s core-image-minimal there are approximately 100 packages necessary to build.
When building chromium it generates a pn-buildlist that requires 140 packages, some of which are shared with core-image-minimal. By adding the chromium package, not only are you getting chromium installed on the image, but you will also have additional dependencies installed to the image.
One package that is installed by chromium is bluez5. Although required for chromium to build, the package is the Bluetooth stack for the browser to communicate on Linux. If the hardware doesn’t utilize Bluetooth, you can save a few MB of storage and RAM removing that.
Another minimization technique, after finishing an image, explore the image work folder and sort the folders by size. This will give you an idea of the largest packages installed before deploying to hardware. The biggest offenders of utilization tend to be graphical environments (x11, wayland), and runtime libraries (rust, clang, qt).
Compilation
Another technique that can be employed to reduce utilization on Yocto is the modification of the compilation flags for the packages being installed on the device. This is something that should already be done in practice for a deployment, as it is part of good security practices to remove debug flags and stripping symbols from software to minimize exposure to potential attackers.
Optimization Flags
Firstly, disable the DEBUG_BUILD by setting the variable to 0 next, ensure all of your optimization is -Os, this will minimize utilization of code by compiling the smallest possible binary. To force this across your system, you can add these options to your local.conf with the FULL_OPTIMISATION = "-Os" variable. A caveat to this is that it will cause issues in debugging, so do not use these features during active development.
Switching the C Library
Another option is switching to the musl C library to implement the standard C library in your image. Musl’s focus is to create a lightweight and small implementation of the C library. If compatibility is a concern I would suggest against this. Glibc is part of the GNU family of libraries which hosts much stronger support than musl. Musl may not be compatible with the system you are building against, or it may have worse runtime performance than a glibc install.
Tuning Your Kernel
The Linux kernel can also be a great vector for minimization, which is one of the larger components on the system. By making sure that the compilation of the Linux kernel only includes the absolute bare minimum for your system, it will reduce overall memory utilization.
Removing unnecessary drivers through adjusting your system’s kernel configuration will reduce both security risks and memory utilization. A great rule of thumb is if you don’t need it, remove it; leaving a bloated kernel takes away potential system memory that could be used by your application.
zram and zswap
At runtime you can add startup flags to limit how much system memory the kernel utilizes, and flags for utilizing zram or zswap. Both zram and zswap require kernel config settings and system hardware. If your system can leverage this, you could utilize these to squeeze out some extra system memory. Both implementations will require validation, as they both have their own sets of benefits and drawbacks.
Tuning Your Init System
System initialization can also make or break how much RAM is utilized, as it will start up every component of the image.
Two major players when it comes to the startup of the system are busybox-init and systemd.
Busybox-init is good for if you need a barebones system initialization, as the requirement is only 1M to 2M of memory compared to systemd’s 20M+. Busybox-init requires tuning and setup to run since it is dependent on a sequential boot and doesn’t have any process monitoring to manage processes.
Although systemd is a burden when it comes to memory, it’s one of the system components that works out of the box as expected. I find that the trade-off in memory can be worth the support for systemd, as the utilization of dbus, sockets, and the ability to concurrently spawn processes outweighs the memory cost savings that busybox-init provides.
If the system can handle a very barebones init manager, and doesn’t require any process monitoring, busybox can be a good alternative for reducing memory.
Trimming systemd Units
Trimming down the system startup will have a large impact on memory utilization.
As stated before, systemd is memory heavy as it runs monitors and system level daemons to manage processes. Removing features that are not necessary for your system will reduce memory utilization.
This can be done at a distro level or package level by utilizing DISTRO_FEATURES:remove = "<feature>" or PACKAGECONFIG:remove:pn-systemd = "<features>". Every unit loaded into systemd, regardless of if its state takes up memory. Because of this, we implore bushwhacking as removing unnecessary packages reduces the units loaded into the systemd daemon.
Finally, a custom systemd/journald.conf file will cap off memory usage with the RuntimeMaxUse= variable and turn off log forwarding by disabling the options for logging.
Results
To quantify these changes, core-image-sato was built from the Yocto Project’s manual as a baseline image. The following tests were done sequentially:
- adding the compilation flags for full optimization,
- modifying kernel parameters,
- trimming the system startup,
- and finally removing x11as the bushwhacking step, since it is the largest package on the system.
The command cat /proc/meminfo was used to check the Slab, Kernel Stack, Vmalloc Used, and Page Tables on the image.
| Change to System | Slab (kb) | Kernel Stack (kb) | Vmalloc Used (kb) | Page Tables (kb) |
|---|---|---|---|---|
| Base image | 26980 | 2704 | 8956 | 3544 |
| Adding Compile Flags | 26980 | 2704 | 8975 | 3544 |
| Modifying Kernel configs (SLUB_TINY) | 23292 | 2704 | 8956 | 3576 |
| Modifying Kernel configs (SLUB_TINY + NO NETWORK) | 22432 | 2704 | 8816 | 3552 |
| Removing x11 | 17900 | 2036 | 8140 | 1632 |
Similar tests were ran by running dmesg | grep -i memory:
| Change to System | kernel code (kb) | rwdat (kb) | rodata (kb) | init (kb) | bss (kb) | reserved (kb) |
|---|---|---|---|---|---|---|
| Base image | 20146K | 2440K | 6184K | 3648k | 1904k | 49744k |
| Modifying Kernel configs (SLUB_TINY) | 20136k | 2452K | 6180K | 3644k | 1904k | 49728k |
| Modifying Kernel configs (SLUB_TINY + NO NETWORK) | 19517k | 2427K | 5932K | 3564k | 2004k | 47676k |
| Removing x11 | 19517k | 2427k | 5932k | 3564k | 2004k | 47680k |
And tests were ran against systemd memory utilization with systemd-cgtop -m:
| Change to System | system.slice | system.slice/xserver-nodm.service | / |
|---|---|---|---|
| Base image | 410M | 75.1M | N/A |
| Modifying Kernel configs (SLUB_TINY) | 410M | 73M | 138M |
| Modifying Kernel configs (SLUB_TINY + NO NETWORK) | 410M | 71M | 125M |
| Removing networkd and modifying journal rules | 408M | 70M | 125M |
| Removing x11 | 100M | N/A | 71M |
Removing x11 altogether had the highest impact on system memory utilization.
The next best was enabling SLUB_TINY for a more memory optimized kernel.
Adding the compilation flags and removing debug didn’t have much effect; these are generally on by default by Yocto.
Removing packages and modifying the systemd services did have a measurable impact on the image as well.
These are not exhaustive tests; more features could have been disabled at the kernel level, and packages and features could have been removed. Testing against real hardware rather than the qemu emulator would likely show significant change, since hardware-specific packages and configuration options come into play that an emulator wouldn’t require.
Conclusion
By utilizing Yocto as your build environment, you have nearly complete and total control of the components that are going into your system, and how they are configured.
By configuring the system with memory saving options and removing the features that aren’t necessary, you can find that maybe you had the ability all along to trim down your board support package to fit into a lower memory specification machine.
Currently there is no telling how the market will shift in the next few years, or if we will have a return to normal market prices for hardware. The best thing that we can do is to ensure that when developing a system, it can be optimized to the best of its ability, to ensure that if the market changes, you’ll be ready for it.



