Integrating Prebuilt Debian Packages into Isar Image Builds

David Krause11 min read
Other TopicSiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Overview

The Isar build system layers a BitBake-style meta-recipe model on top of Debian's binary package model. When a target image is assembled, two parallel dependency chains resolve: the BitBake recipe level (which decides what gets compiled or fetched) and the Debian package level (which decides what is unpacked onto the final rootfs). For application code that already exists as a tested .deb artifact, integrating that file is a matter of teaching the Isar layer how to map a recipe name to a binary package name, and how to feed that binary into the image assembly.

This guide walks through a minimal working configuration based on the public prebuilt-deb reference for the Siemens IOT2050 Isar example. The pattern generalizes to any Isar-based image (Raspberry Pi, generic amd64, custom boards) and is the same path that gets followed for runtime payloads such as node-red, tcf-agent, or proprietary customer binaries.

Isar Build System Architecture

Isar (Intelligent System for Automated Rootfs assembly) is the Debian-flavored build system maintained by Siemens for the IOT2050. It runs inside kas and uses BitBake as its task scheduler. Conceptually it is the Debian analogue of the Yocto/OpenEmbedded stack: same task engine, different package format and a different set of base classes.

Two dependency planes are visible to every recipe:

  1. BitBake recipe plane. IMAGE_INSTALL is parsed by BitBake and triggers the build of any recipe that lists the requested identifier in its PROVIDES list. The recipe is responsible for producing one or more binary Debian packages as its output.
  2. Debian package plane. The actual rootfs is assembled by collecting the .deb files produced by the recipes. IMAGE_INSTALL is also forwarded (after the recipe name has been resolved to a package name) to the rootfs tool so that the right packages end up on the filesystem.

The split matters when a recipe generates more than one binary package, or when the recipe's own name does not match the package it produces. In that case the recipe must extend PROVIDES so that asking for the package identifier triggers the recipe build. Without that extension, BitBake will not know which recipe to schedule and the image build will either silently skip the package or fail at rootfs time with unable to locate package.

Prerequisites

  • A working Isar build environment built from kas-iot2050-example.yml (or an equivalent kas project file) on a Linux build host with Docker or Podman.
  • kas-container in $PATH.
  • A valid .deb file (architecture-correct, dependency-complete) that you have already installed and tested on a reference rootfs. fpm from Jordan Sissel is the recommended tool to assemble such files from self-compiled binaries because it lets you keep a pre-flight installable package that you can run, instrument, and validate before ever wiring it into a build.
  • Access to the image recipe in the recipes-app directory of the Isar project, or a user-specific layer added to bblayers.conf.
Important: The host-side test of the .deb is the only reliable way to detect runtime dependency issues, missing shared libraries, or incorrect maintainer scripts. Isar's dpkg-prebuilt class will not rebuild or patch a foreign .deb; it only injects it into the package feed.

Directory Layout

The reference layout used in the source thread puts the recipe alongside the .deb artifact inside the recipes-app/ tree. The example directory name is prebuilt-deb:

recipes-app/
  prebuilt-deb/
    prog_1.0.0_amd64.deb
    prog_1.0.0.bb

The two-file layout keeps the binary and its recipe co-located. kas will pick up the .bb file automatically because the parent directory recipes-app is already declared in bblayers.conf by the upstream kas-iot2050-example.yml configuration.

The dpkg-prebuilt.bbclass Mechanism

Standard Isar recipe classes such as dpkg or debian expect to be pointed at a source tree from which the build derives the binary package. For a prebuilt .deb that is wasted work, and it is also unnecessary because the binary already exists.

dpkg-prebuilt.bbclass is a thin recipe class shipped with Isar that:

  1. Declares DPKG_PREBUILT_PKG to point at the .deb artifact inside the recipe directory.
  2. Skips any compile, patch, or configure step because the binary is already final.
  3. Generates a single binary Debian package whose name matches the binary package inside the .deb (not the recipe name).
  4. Adds that binary package to Isar's package index so that IMAGE_INSTALL resolution finds it.

The key behavior to remember is that the recipe name and the binary package name are independent. The recipe file can be named prog_1.0.0.bb while the package inside the .deb can be myapp. The class is designed to bridge that gap.

PROVIDES, RPROVIDES, and Package Name Resolution

BitBake uses PROVIDES to advertise identifiers a recipe can satisfy. By default, a recipe called prog_1.0.0.bb provides prog-1.0.0. If the binary .deb exposes a package called myapp, then:

  • IMAGE_INSTALL += "myapp" resolves to "build the recipe that provides myapp".
  • Without an explicit PROVIDES entry, myapp is not on the provider list and the build will fail with a missing-provider error.

To close the gap, the recipe must extend its PROVIDES list to include the binary package name. The minimal extension looks like this:

PROVIDES += "myapp"
RPROVIDES:${PN} += "myapp"

The first line makes BitBake schedule this recipe when myapp is requested. The second line ensures that packages depending on myapp at the Debian level will see it as satisfied on the assembled rootfs. ${PN} resolves to the recipe name (prog-1.0.0) at parse time.

Field note: If your .deb produces multiple binary packages (e.g. myapp, myapp-dev, myapp-dbg), each one must appear in PROVIDES. Otherwise only the first Provides field of the control archive is exposed. Inspect the .deb with dpkg-deb -I prog_1.0.0_amd64.deb to enumerate them.

Step-by-Step Integration

  1. Stage the artifact and the recipe. Copy the tested .deb and the matching .bb file into recipes-app/prebuilt-deb/ inside the Isar project. The recipe file should inherit dpkg-prebuilt and set DPKG_PREBUILT_PKG to the relative path of the .deb.
  2. Verify the binary package name. Run dpkg-deb -f prog_1.0.0_amd64.deb Package and note the value. This is the identifier you will need in PROVIDES and in IMAGE_INSTALL.
  3. Check declared dependencies. Run dpkg-deb -f prog_1.0.0_amd64.deb Depends. Any dependency that is not already in the base image must be added to IMAGE_INSTALL explicitly.
  4. Edit the recipe to extend PROVIDES. For a single binary package named myapp:
    inherit dpkg-prebuilt
    
    SRC_URI = "file://prog_1.0.0_amd64.deb"
    DPKG_PREBUILT_PKG = "${WORKDIR}/prog_1.0.0_amd64.deb"
    
    PROVIDES += "myapp"
    RPROVIDES:${PN} += "myapp"
    
    do_install[noexec] = "1"
    The do_install[noexec] line is defensive: the prebuilt class already skips install, but it prevents a stray override from re-enabling it.
  5. Update the image recipe. In the .bb file that defines the target image (the file referenced by the image target in kas-iot2050-example.yml), add the binary package to IMAGE_INSTALL. The upstream example removed node-red and tcf-agent for the test; a clean integration is:
    IMAGE_INSTALL += "myapp"
  6. Trigger the build. From the project root, run the standard kas-container invocation:
    ./kas-container build kas-iot2050-example.yml
    This single command resolves layers, fetches sources, schedules the prebuilt recipe, and finally assembles the rootfs with the new package included.

Building the .deb Artifact with fpm

For self-compiled application code, fpm is the path of least resistance. A typical build of a single binary, a systemd unit, and a configuration file looks like:

fpm -s dir -t deb \
    -n myapp -v 1.0.0 \
    --architecture amd64 \
    --depends "libc6 (>= 2.31)" \
    --depends "libssl3" \
    --after-install postinst.sh \
    --before-remove prerm.sh \
    build/myapp=/usr/bin/myapp \
    systemd/myapp.service=/etc/systemd/system/myapp.service \
    conf/myapp.conf=/etc/myapp/myapp.conf

Three details are worth flagging:

  • Test first, integrate second. The reason fpm is the right tool is that you can sudo dpkg -i myapp_1.0.0_amd64.deb on any Debian host, run the program, exercise its maintainer scripts, and only after that success copy the artifact into the Isar tree. This is the workflow that eliminates the most common class of integration bugs.
  • Pin the architecture. --architecture amd64 must match the IOT2050's target (typically amd64 for the IOT2050-SDK, arm64 for IOT2050 variants built on the arm64 base image). A mismatch causes dpkg to refuse the package at rootfs time with package architecture (arm64) does not match system (amd64).
  • Declare runtime dependencies explicitly. Isar will not pull in transitive dependencies that you forgot to list. --depends entries become Depends: in the .deb control archive, and Isar resolves them through its own package index.

Verification

After ./kas-container build kas-iot2050-example.yml completes, validate the image with three independent checks.

  1. Inspect the package index. In the build directory, locate the generated Package.gz file and grep for the new package:
    zcat tmp/deploy/deb-*/Packages.gz | grep -A 5 "^Package: myapp"
    Confirm that Package: myapp, the correct Version, and Architecture: amd64 (or arm64) are present.
  2. Mount the rootfs and list the files. The image recipe will produce a tar or ext4 rootfs. Extract or mount it and confirm the binary landed at the expected path:
    tar -tf tmp/deploy/images/iot2050/iot2050-image-iot2050.tar.gz | grep myapp
  3. Flash and run. Write the image to the IOT2050 (or boot it in QEMU with the kas-iot2050 machine definition) and verify that dpkg -l myapp shows ii status, that the systemd unit is enabled if applicable, and that the program starts cleanly.

Troubleshooting Matrix

Symptom Probable Cause Fix
Nothing PROVIDES 'myapp' Recipe does not extend PROVIDES with the binary package name Add PROVIDES += "myapp" and RPROVIDES:${PN} += "myapp" to the .bb
Package built but not in rootfs IMAGE_INSTALL uses the recipe name instead of the binary package name Update IMAGE_INSTALL to use the package identifier from dpkg-deb -f
package architecture (X) does not match system (Y) --architecture mismatch when building with fpm Rebuild the .deb with the correct architecture flag
dependency not satisfiable: libfoo Runtime Depends of the .deb not met by base image Add the missing package to IMAGE_INSTALL or to the base image recipe
Recipe re-runs every build with no source change Missing SRC_URI checksum or do_fetch override still firing Set SRC_URI = "file://..." with a stable filename and inherit dpkg-prebuilt
Multiple binary packages, only one visible Only the first Package: field advertised via PROVIDES List every package name in PROVIDES
fakeroot dpkg-buildpackage errors during recipe execution Wrong recipe class inherited; trying to rebuild a prebuilt Switch to inherit dpkg-prebuilt and ensure no other dpkg or debian class is inherited

Multi-Package and Complex Dependency Patterns

Real applications rarely ship as a single .deb. The same recipe pattern scales:

  • Library + app + debug split. Build three .deb files with fpm, name them mylib, myapp, and myapp-dbg. List all three in PROVIDES and let IMAGE_INSTALL pick the runtime subset.
  • Versioned upgrades. Bumping PV in the recipe (prog_1.0.1.bb) is enough to trigger a rebuild of the image. If the new .deb ships a different package name, update PROVIDES accordingly; if only the upstream version changed, the file replacement is automatic.
  • Per-board variants. For projects that target both the IOT2050 (amd64) and an arm64 variant, parameterize DPKG_PREBUILT_PKG with a MACHINE_ARCH conditional:
    DPKG_PREBUILT_PKG = "${WORKDIR}/prog_1.0.0_${MACHINE_ARCH}.deb"
    This lets one recipe drive both images from a single kas project.
  • Signed packages. For production builds, sign the .deb with dpkg-sig --sign builder and configure Isar's feed mechanism to verify the signature. The dpkg-prebuilt class does not validate signatures, so the check must be added at the feed level.

Operational Checklist

Before declaring a prebuilt package integration complete, run through this list:

  1. Does dpkg-deb -I show the expected Package, Version, Architecture, and Depends?
  2. Is the recipe's PROVIDES aligned with every binary package inside the .deb?
  3. Is the package identifier in IMAGE_INSTALL the binary package name, not the recipe name?
  4. Is do_install[noexec] set to defend against accidental overrides?
  5. Has the .deb been installed and tested on a reference host before integration?
  6. Has the resulting image been flashed or emulated, with dpkg -l confirming installation?
Safety: Treat any .deb integrated via dpkg-prebuilt as a black box. The class does not run the upstream build, does not apply security patches, and does not regenerate the binary when the source changes. Patch management is your responsibility; Isar is the delivery vehicle, not the security boundary.

FAQ

What recipe class handles a prebuilt .deb in Isar?

Use dpkg-prebuilt.bbclass. Inherit it with inherit dpkg-prebuilt and set DPKG_PREBUILT_PKG to the path of the .deb inside ${WORKDIR}. The class skips the compile/install pipeline and emits a single binary package whose name comes from the control archive inside the .deb.

Why does my prebuilt package show up in IMAGE_INSTALL resolution but not on the rootfs?

This usually means IMAGE_INSTALL lists the recipe name (for example prog-1.0.0) while the binary package inside the .deb is named differently (for example myapp). BitBake schedules the recipe because PROVIDES maps the recipe to the binary, but the rootfs assembler still needs the binary identifier. Replace the recipe name in IMAGE_INSTALL with the package name from dpkg-deb -f file.deb Package.

How do I integrate a multi-package .deb set built with fpm?

Build one .deb per component (runtime, devel, debug) and add every package name from dpkg-deb -f to PROVIDES in the recipe. Then list the runtime packages in IMAGE_INSTALL. The dpkg-prebuilt class can carry a single .deb; for multiple artifacts, use a wrapper recipe that stages each one with its own DPKG_PREBUILT_PKG or chain recipes through RDEPENDS.

Can I use a prebuilt .deb on both amd64 and arm64 IOT2050 images?

Yes. Parameterize DPKG_PREBUILT_PKG with ${MACHINE_ARCH} and ship one .deb per architecture in the recipe's SRC_URI. The upstream kas-iot2050 configuration exposes MACHINE variables that map cleanly to amd64 and arm64 targets.

How do I verify the integrated package on a running IOT2050?

After flashing, run dpkg -l <package-name> to confirm installation status, dpkg -L <package-name> to enumerate installed files, and any service-specific check such as systemctl status <unit>. If the package is missing, inspect tmp/deploy/deb-*/Packages.gz from the build to confirm it reached the package index.

Back to blog