[RFC,1/1] rootfs: use host apt to install packages into rootfs

Message ID 20260910100700.3998213-2-felix.moessbauer@siemens.com
State New
Headers show
Series Use host apt to reduce buildtime and size of rootfs | expand

Commit Message

Felix Moessbauer Sept. 10, 2026, 10:07 a.m. UTC
We currently use the apt inside the rootfs to operate on the rootfs.
This has two major disadvantages:

1. on non-native, the apt invocations are emulated (including the
   extraction of the packages and downloading)
2. apt must be installed in the rootfs (which makes the rootfs larger
   and pulls in a lot of static-build-using dependencies related to the
   rust parts, which is relevant for license clearing)

We change that similar to how mmdebstrap is doing it: Use the host apt
to operate on the chroot, whereby dpkg itself runs inside the chroot to
have proper support for the maintainer scripts. By that, apt is not
emulated and the generated chroots can be much smaller.

Not having apt inside the chroot exposes packaging bugs that assume that
it (or any of its dependencies) is available. To work around these, we
explicitly install the needed packages on a case-by-case decision.

As of now, this series is only CI tested for rootless and also does not
support the base-apt case.

Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
---
 meta-isar/conf/distro/raspios-bullseye.conf   |   2 +
 meta-isar/conf/distro/ubuntu-focal.conf       |   3 +
 meta/classes-recipe/deb-dl-dir.bbclass        |  34 ++++
 meta/classes-recipe/dpkg-source.bbclass       |  30 ++--
 .../image-account-extension.bbclass           |   1 +
 .../image-locales-extension.bbclass           |  13 +-
 meta/classes-recipe/rootfs.bbclass            | 145 +++++++-----------
 meta/classes-recipe/sdk.bbclass               |   5 +-
 .../isar-mmdebstrap/isar-mmdebstrap.inc       |  19 +--
 .../sbom-chroot/sbom-chroot.bb                |   2 +-
 .../sbuild-chroot/sbuild-chroot.inc           |   1 +
 11 files changed, 129 insertions(+), 126 deletions(-)

Patch

diff --git a/meta-isar/conf/distro/raspios-bullseye.conf b/meta-isar/conf/distro/raspios-bullseye.conf
index a8b59c09..39b0bc8e 100644
--- a/meta-isar/conf/distro/raspios-bullseye.conf
+++ b/meta-isar/conf/distro/raspios-bullseye.conf
@@ -20,6 +20,8 @@  DISTRO_MM_OPTS += "${MMAPTOPT_NOEXPKEYSIGN}"
 DISTRO_BOOTSTRAP_KEYS = "http://raspbian.raspberrypi.org/raspbian.public.key;sha256sum=ca59cd4f2bcbc3a1d41ba6815a02a8dc5c175467a59bd87edeac458f4a5345de"
 DISTRO_BOOTSTRAP_KEYS:arm64 = ""
 DISTRO_BOOTSTRAP_BASE_PACKAGES:append = " usrmerge"
+# workaround for missing depends to apt in raspbian-archive-keyring
+DISTRO_BOOTSTRAP_BASE_PACKAGES:append = " apt"
 
 DISTRO_KERNELS ?= "kernel kernel7 kernel7l kernel8"
 
diff --git a/meta-isar/conf/distro/ubuntu-focal.conf b/meta-isar/conf/distro/ubuntu-focal.conf
index 155644c1..b6c3e442 100644
--- a/meta-isar/conf/distro/ubuntu-focal.conf
+++ b/meta-isar/conf/distro/ubuntu-focal.conf
@@ -12,3 +12,6 @@  DISTRO_GCC = "9"
 DEBIAN_COMPAT = "12"
 
 DEBIAN_STANDARDS_VERSION ?= "4.5.0"
+
+# workaround for missing depends in /usr/share/initramfs-tools/hooks/fixrtc
+IMAGE_PREINSTALL:append = " e2fsprogs"
diff --git a/meta/classes-recipe/deb-dl-dir.bbclass b/meta/classes-recipe/deb-dl-dir.bbclass
index 60e2fecc..40e1f763 100644
--- a/meta/classes-recipe/deb-dl-dir.bbclass
+++ b/meta/classes-recipe/deb-dl-dir.bbclass
@@ -178,3 +178,37 @@  deb_dl_dir_export() {
         done
 EOF
 }
+
+# Point isar-apt at its real build-path location so host apt can access it.
+# The reproducible 'file:///isar-apt' encodes to the apt list prefix '_isar-apt',
+# the real repo path encodes to the same path with '/' replaced by '_'.
+deb_dl_dir_isar_apt_to_host() {
+    export rootfs="${1}"
+    export host_prefix="$(printf '%s' '${REPO_ISAR_DIR}/${DISTRO}' | tr '/' '_')"
+    run_privileged_heredoc << '    EOSUDO'
+        set -e
+        sed -i 's|file:///isar-apt|file://${REPO_ISAR_DIR}/${DISTRO}|g' \
+            "${rootfs}/etc/apt/sources.list.d/isar-apt.list"
+        for f in "${rootfs}/var/lib/apt/lists/"_isar-apt*; do
+            [ -e "$f" ] || continue
+            suffix="$(basename "$f" | sed 's|^_isar-apt||')"
+            mv "$f" "${rootfs}/var/lib/apt/lists/${host_prefix}${suffix}"
+        done
+    EOSUDO
+}
+
+# Revert the deb_dl_dir_isar_apt_to_host changes.
+deb_dl_dir_isar_apt_to_target() {
+    export rootfs="${1}"
+    export host_prefix="$(printf '%s' '${REPO_ISAR_DIR}/${DISTRO}' | tr '/' '_')"
+    run_privileged_heredoc << '    EOSUDO'
+        set -e
+        sed -i 's|file://${REPO_ISAR_DIR}/${DISTRO}|file:///isar-apt|g' \
+            "${rootfs}/etc/apt/sources.list.d/isar-apt.list"
+        for f in "${rootfs}/var/lib/apt/lists/${host_prefix}"*; do
+            [ -e "$f" ] || continue
+            suffix="$(basename "$f" | sed "s|^${host_prefix}||")"
+            mv "$f" "${rootfs}/var/lib/apt/lists/_isar-apt${suffix}"
+        done
+    EOSUDO
+}
diff --git a/meta/classes-recipe/dpkg-source.bbclass b/meta/classes-recipe/dpkg-source.bbclass
index 97cf9714..9b712741 100644
--- a/meta/classes-recipe/dpkg-source.bbclass
+++ b/meta/classes-recipe/dpkg-source.bbclass
@@ -104,20 +104,26 @@  fetch_common_source_unshare() {
         set -e
         mkdir -p ${UNSHARE_DPKG_SOURCE_CHROOT}
         tar -xf "${SBUILD_CHROOT}" -C ${UNSHARE_DPKG_SOURCE_CHROOT}
+EOF
 
-        ${@insert_isar_mounts(d, d.getVar('UNSHARE_DPKG_SOURCE_CHROOT'), d.getVar('SCHROOT_MOUNTS'))}
-        chroot ${UNSHARE_DPKG_SOURCE_CHROOT} /bin/bash -s <<'EOAPT'
-            set -e
-            apt-get update \
-                -o Dir::Etc::SourceList="sources.list.d/isar-apt.list" \
-                -o Dir::Etc::SourceParts="-" \
-                -o APT::Get::List-Cleanup="0"
+    deb_dl_dir_isar_apt_to_host ${UNSHARE_DPKG_SOURCE_CHROOT}
 
-            cd /work
-            apt-get -y --download-only --only-source \
-                -o Debug::NoLocking=1 -o Acquire::Source-Symlinks="false"  \
-                source ${DEBIAN_SOURCE}
-EOAPT
+    run_privileged_heredoc <<'EOF'
+        ${@insert_isar_mounts(d, d.getVar('UNSHARE_DPKG_SOURCE_CHROOT'), d.getVar('SCHROOT_MOUNTS'))}
+        apt-get update \
+            -o Dir="${UNSHARE_DPKG_SOURCE_CHROOT}" \
+            -o APT::Architecture="${HOST_ARCH}" \
+            -o Dir::Etc::SourceList="sources.list.d/isar-apt.list" \
+            -o Dir::Etc::SourceParts="-" \
+            -o APT::Get::List-Cleanup="0"
+
+        cd /work
+        apt-get \
+            -o Dir="${UNSHARE_DPKG_SOURCE_CHROOT}" \
+            -o APT::Architecture="${HOST_ARCH}" \
+            -y --download-only --only-source \
+            -o Debug::NoLocking=1 -o Acquire::Source-Symlinks="false"  \
+            source ${DEBIAN_SOURCE}
 EOF
 
     # run cleanup in separate session to ensure nothing is mounted
diff --git a/meta/classes-recipe/image-account-extension.bbclass b/meta/classes-recipe/image-account-extension.bbclass
index dd70f2a3..8c7a3270 100644
--- a/meta/classes-recipe/image-account-extension.bbclass
+++ b/meta/classes-recipe/image-account-extension.bbclass
@@ -140,6 +140,7 @@  def image_create_users(d: "DataSmart") -> None:
             bb.process.run([*chroot, "/usr/bin/passwd", "--expire", entry])
 
 
+ROOTFS_PACKAGES += "passwd"
 ROOTFS_POSTPROCESS_COMMAND += "image_postprocess_accounts"
 image_postprocess_accounts[vardeps] += "USERS GROUPS"
 python image_postprocess_accounts() {
diff --git a/meta/classes-recipe/image-locales-extension.bbclass b/meta/classes-recipe/image-locales-extension.bbclass
index c03f34c0..a8a3201b 100644
--- a/meta/classes-recipe/image-locales-extension.bbclass
+++ b/meta/classes-recipe/image-locales-extension.bbclass
@@ -32,8 +32,10 @@  image_install_localepurge_download() {
     run_privileged_heredoc <<'EOF'
     set -e
     ${@insert_isar_mounts(d, d.getVar('ROOTFSDIR'), d.getVar('ROOTFS_MOUNTS') if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else '')}
-    chroot ${ROOTFSDIR} \
-        /usr/bin/apt-get ${ROOTFS_APT_ARGS} -o Debug::NoLocking=1 --download-only localepurge
+    /usr/bin/apt-get ${ROOTFS_APT_ARGS} \
+        -o Dir="${ROOTFSDIR}" \
+        -o APT::Architecture="${ROOTFS_ARCH}" \
+        -o Debug::NoLocking=1 --download-only localepurge
 EOF
 }
 
@@ -78,8 +80,9 @@  __EOF__
             echo 'localepurge was not installed (removing it later)'
             # track additional packages that will be installed, as these packages might be
             # in the suggested set of other packages and by that need to be explicitly removed
-            localepurge_pkgs=$(chroot '${ROOTFSDIR}' apt-get ${ROOTFS_APT_ARGS} -s localepurge 2>&1 | sed -n 's/^Inst \([^ ]*\) .*/\1/p')
-            chroot '${ROOTFSDIR}' apt-get ${ROOTFS_APT_ARGS} localepurge
+            localepurge_pkgs=$(/usr/bin/apt-get \
+                ${ROOTFS_HOST_APT_OPTS} ${ROOTFS_APT_ARGS} -s localepurge 2>&1 | sed -n 's/^Inst \([^ ]*\) .*/\1/p')
+            /usr/bin/apt-get ${ROOTFS_HOST_APT_OPTS} ${ROOTFS_APT_ARGS} localepurge
         fi
 
         cat '${WORKDIR}/locale.gen' >> '${ROOTFSDIR}/etc/locale.gen'
@@ -115,7 +118,7 @@  EOSH
         if [ "$localepurge_state" = 'p' ]
         then
             echo removing localepurge...
-            chroot '${ROOTFSDIR}' apt-get purge --yes $localepurge_pkgs
+            /usr/bin/apt-get ${ROOTFS_HOST_APT_OPTS} purge --yes $localepurge_pkgs
         fi
 EOSUDO
 }
diff --git a/meta/classes-recipe/rootfs.bbclass b/meta/classes-recipe/rootfs.bbclass
index cf996f5c..136a8861 100644
--- a/meta/classes-recipe/rootfs.bbclass
+++ b/meta/classes-recipe/rootfs.bbclass
@@ -59,6 +59,16 @@  ROOTFS_FEATURES:remove:focal = "generate-sbom"
 # Capture all information needed for sbom generation
 ROOTFS_APT_STATE = "apt-state.tar.zst"
 ROOTFS_APT_ARGS="install --yes -o Debug::pkgProblemResolver=yes"
+ROOTFS_HOST_APT_OPTS = "-o Dir=${ROOTFSDIR} -o APT::Architecture=${ROOTFS_ARCH} -o DPkg::Chroot-Directory=${ROOTFSDIR}"
+
+# The host apt-get used to populate the rootfs must honor the rootfs' own apt
+# configuration fragments instead of the host's /etc/apt. A command-line '-o' is
+# applied too late (after apt has already read its config parts), so point apt at
+# the chroot via APT_CONFIG, which is parsed during apt initialization - before
+# the config parts directory is read. No host file is touched.
+ROOTFS_HOST_APT_CONFIG = "${WORKDIR}/isar-host-apt.conf"
+# Wrapper to run the host apt-get with that configuration.
+HOST_APT_CMD = "env APT_CONFIG=${ROOTFS_HOST_APT_CONFIG} /usr/bin/apt-get ${ROOTFS_HOST_APT_OPTS}"
 
 ROOTFS_CLEAN_FILES="/etc/hostname /etc/resolv.conf"
 
@@ -68,7 +78,7 @@  ROOTFS_PACKAGE_SUFFIX ?= "${PN}-${DISTRO}-${DISTRO_ARCH}"
 ROOTFS_STUBS_DIR = "/usr/local/isar-sbin"
 
 # list of <outer>:<inner> or <outer> mount entries
-ROOTFS_MOUNTS ??= "${REPO_ISAR_DIR}/${DISTRO}:/isar-apt ${WORKDIR}:/isar-work"
+ROOTFS_MOUNTS ??= "${WORKDIR}:/isar-work"
 
 python () {
     mounts = d.getVar('ROOTFS_MOUNTS', False)
@@ -95,75 +105,6 @@  export LANG = "${LOCALE_DEFAULT}"
 export LANGUAGE = "${LOCALE_DEFAULT}"
 export LC_ALL = "${LOCALE_DEFAULT}"
 
-# Execute a command against a rootfs and with isar-apt bind-mounted.
-# Additional mounts may be specified using --bind <source> <target> and a
-# custom directory for the command to be executed with --chdir <dir>. The
-# command is assumed to follow the special "--" argument. This would replace
-# "sudo chroot" calls especially when a native command may be used instead of
-# chroot'ed command and without elevated privileges (the command will likely
-# take the rootfs as argument; e.g. apt-get -o Dir=${ROOTFSDIR}). If the
-# optional rootfs argument is omitted, the host rootfs will be used (e.g. to
-# run native commands): this should be used with care.
-#
-# Usage: rootfs_cmd [options] [rootfs] -- command
-#
-rootfs_cmd() {
-    set -- "$@"
-    bwrap_args="--bind ${REPO_ISAR_DIR}/${DISTRO} /isar-apt"
-    bwrap_binds=""
-    bwrap_rootfs=""
-
-    while [ "${#}" -gt "0" ] && [ "${1}" != "--" ]; do
-        case "${1}" in
-            --bind)
-                if [ "${#}" -lt "3" ]; then
-                    bbfatal "--bind requires two arguments"
-                fi
-                bwrap_binds="${bwrap_binds} --bind ${2} ${3}"
-                shift 3
-                ;;
-            --chdir)
-                if [ "${#}" -lt "2" ]; then
-                    bbfatal "${1} requires an argument"
-                fi
-                bwrap_args="${bwrap_args} ${1} ${2}"
-                shift 2
-                ;;
-            -*)
-                bbfatal "${1} is not a supported option!"
-                ;;
-            *)
-                if [ -z "${bwrap_rootfs}" ]; then
-                    bwrap_rootfs="${1}"
-                    shift
-                else
-                    bbfatal "unexpected argument '${1}'"
-                fi
-                ;;
-        esac
-    done
-
-    if [ -n "${bwrap_rootfs}" ]; then
-        bwrap_args="${bwrap_args} --bind ${bwrap_rootfs} /"
-    fi
-
-    if [ "${#}" -le "1" ] || [ "${1}" != "--" ]; then
-        bbfatal "no command specified (missing --)"
-    fi
-    shift  # remove "--", command and its arguments follows
-
-    # bin, lib and lib64 are only real directories on unmerged-usr rootfs
-    for ro_d in bin etc lib lib64 sys usr var; do
-        [ -d ${bwrap_rootfs}/${ro_d} ] && [ ! -L ${bwrap_rootfs}/${ro_d} ] || continue
-        bwrap_args="${bwrap_args} --ro-bind ${bwrap_rootfs}/${ro_d} /${ro_d}"
-    done
-
-    bwrap --unshare-user --unshare-pid ${bwrap_args} \
-        --dev-bind /dev /dev --proc /proc --tmpfs /tmp \
-        ${@'--bind "${REPO_ISAR_DIR}/${DISTRO}" /isar-apt' if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else ''} \
-        ${bwrap_binds} -- "${@}"
-}
-
 rootfs_do_mounts[weight] = "3"
 python rootfs_do_mounts() {
     if d.getVar('ISAR_CHROOT_MODE') == 'schroot':
@@ -277,6 +218,20 @@  EOF
 EOSUDO
 }
 
+ROOTFS_CONFIGURE_COMMAND += "rootfs_redirect_isar_apt_to_host"
+rootfs_redirect_isar_apt_to_host() {
+    if [ -f "${ROOTFSDIR}/etc/apt/sources.list.d/isar-apt.list" ]; then
+        deb_dl_dir_isar_apt_to_host "${ROOTFSDIR}"
+    fi
+}
+
+# restore by latest before capturing the apt state and before sstate caching
+rootfs_redirect_isar_apt_to_target() {
+    if [ -f "${ROOTFSDIR}/etc/apt/sources.list.d/isar-apt.list" ]; then
+        deb_dl_dir_isar_apt_to_target "${ROOTFSDIR}"
+    fi
+}
+
 ROOTFS_CONFIGURE_COMMAND += "rootfs_configure_apt"
 rootfs_configure_apt[weight] = "2"
 rootfs_configure_apt() {
@@ -299,6 +254,14 @@  rootfs_configure_apt() {
 EOSUDO
 }
 
+# Point the host apt-get at the rootfs' apt.conf.d (see ROOTFS_HOST_APT_CONFIG).
+ROOTFS_CONFIGURE_COMMAND =+ "rootfs_configure_host_apt_config"
+rootfs_configure_host_apt_config[weight] = "1"
+rootfs_configure_host_apt_config() {
+    echo 'Dir::Etc::parts "${ROOTFSDIR}/etc/apt/apt.conf.d";' \
+        > '${ROOTFS_HOST_APT_CONFIG}'
+}
+
 ROOTFS_CONFIGURE_COMMAND += "rootfs_disable_initrd_generation"
 rootfs_disable_initrd_generation[weight] = "1"
 rootfs_disable_initrd_generation() {
@@ -323,7 +286,7 @@  rootfs_install_pkgs_update() {
     run_privileged_heredoc <<'EOF'
         set -e
         ${@insert_isar_mounts(d, d.getVar('ROOTFSDIR'), d.getVar('ROOTFS_MOUNTS')) if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else ''}
-        chroot '${ROOTFSDIR}' /usr/bin/apt-get update \
+        ${HOST_APT_CMD} update \
             -o Dir::Etc::SourceList="sources.list.d/isar-apt.list" \
             -o Dir::Etc::SourceParts="-" \
             -o APT::Get::List-Cleanup="0"
@@ -354,10 +317,14 @@  rootfs_install_pkgs_download[progress] = "custom:rootfs_progress.PkgsDownloadPro
 rootfs_install_pkgs_download[isar-apt-lock] = "release-after"
 rootfs_install_pkgs_download[network] = "${TASK_USE_NETWORK}"
 rootfs_install_pkgs_download() {
-    # download packages using apt in a non-privileged namespace
-    rootfs_cmd --bind "${ROOTFSDIR}/var/cache/apt/archives" /var/cache/apt/archives \
-               ${ROOTFSDIR} \
-               -- /usr/bin/apt-get ${ROOTFS_APT_ARGS} -o Debug::NoLocking=1 --download-only ${ROOTFS_PACKAGES}
+    run_privileged_heredoc <<'EOF'
+        set -e
+        ${@insert_isar_mounts(d, d.getVar('ROOTFSDIR'), d.getVar('ROOTFS_MOUNTS')) if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else ''}
+        ${HOST_APT_CMD} \
+            ${ROOTFS_APT_ARGS} \
+            -o Debug::NoLocking=1 \
+            --download-only ${ROOTFS_PACKAGES}
+EOF
 }
 
 ROOTFS_INSTALL_COMMAND_BEFORE_EXPORT ??= ""
@@ -373,16 +340,15 @@  ROOTFS_INSTALL_COMMAND += "rootfs_install_pkgs_isar_download"
 rootfs_install_pkgs_isar_download[weight] = "50"
 rootfs_install_pkgs_isar_download[isar-apt-lock] = "acquire-before release-after"
 rootfs_install_pkgs_isar_download() {
-    # Command apt-get install do not cache packages from local repos
-    # We can obtain non cached package URIs by recalling install command here
-    # No need to export those files to dl_dir, so we can run it right after
-    rootfs_cmd --bind "${ROOTFSDIR}/var/cache/apt/archives" /var/cache/apt/archives \
-               --chdir "/var/cache/apt/archives" \
-               ${ROOTFSDIR} \
-               -- /usr/bin/sh -c 'apt-get ${ROOTFS_APT_ARGS} --print-uris ${ROOTFS_PACKAGES} | \
-                                  sed -n "s|^.file:\(/[^'\'']*\.deb\). \([^ ]*\.deb\).*|\1 \2|p" | \
-                                  sed ":a; s|^\([^ ]*\)%|\1\\\\x|; ta" | \
-                                  while read -r path name; do cp -n "$(/usr/bin/printf "%b" "$path")" "$name" ; done'
+    run_privileged_heredoc <<'EOF'
+        set -e
+        ${@insert_isar_mounts(d, d.getVar('ROOTFSDIR'), d.getVar('ROOTFS_MOUNTS')) if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else ''}
+        ${HOST_APT_CMD} \
+            ${ROOTFS_APT_ARGS} --print-uris ${ROOTFS_PACKAGES} | \
+                sed -n "s|^.file:\(/[^'\'']*\.deb\). \([^ ]*\.deb\).*|\1 \2|p" | \
+                sed ":a; s|^\([^ ]*\)%|\1\\\\x|; ta" | \
+                while read -r path name; do cp -n "$(/usr/bin/printf "%b" "$path")" "${ROOTFSDIR}/var/cache/apt/archives/$name" ; done
+EOF
 }
 
 ROOTFS_INSTALL_COMMAND += "${@ 'rootfs_install_clean_files' if (d.getVar('ROOTFS_CLEAN_FILES') or '').strip() else ''}"
@@ -403,8 +369,7 @@  rootfs_install_pkgs_install() {
     run_privileged_heredoc <<'EOF'
     set -e
     ${@insert_isar_mounts(d, d.getVar('ROOTFSDIR'), d.getVar('ROOTFS_MOUNTS')) if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else ''}
-    chroot "${ROOTFSDIR}" \
-        /usr/bin/apt-get ${ROOTFS_APT_ARGS} --no-download ${ROOTFS_PACKAGES}
+    ${HOST_APT_CMD} ${ROOTFS_APT_ARGS} --no-download ${ROOTFS_PACKAGES}
 EOF
 }
 
@@ -424,7 +389,7 @@  rootfs_clear_initrd_symlinks() {
     run_privileged rm -f ${ROOTFSDIR}/initrd.img.old
 }
 
-ROOTFS_INSTALL_COMMAND += "${@bb.utils.contains('ROOTFS_FEATURES', 'generate-sbom', 'rootfs_capture_apt_state', '', d)}"
+ROOTFS_POSTPROCESS_COMMAND:prepend = "${@bb.utils.contains('ROOTFS_FEATURES', 'generate-sbom', 'rootfs_capture_apt_state', '', d)} "
 rootfs_capture_apt_state() {
     ( cd ${ROOTFSDIR} && find usr/share/doc -name copyright -print0 ) | \
     tar -cf ${WORKDIR}/${ROOTFS_APT_STATE} --zstd --sort=name \
@@ -437,6 +402,7 @@  rootfs_capture_apt_state() {
         var/lib/apt/extended_states \
         var/lib/dpkg/status
 }
+ROOTFS_POSTPROCESS_COMMAND:prepend = "rootfs_redirect_isar_apt_to_target "
 
 do_rootfs_install[root_cleandirs] = "${ROOTFSDIR}"
 do_rootfs_install[cleandirs] += "${DEPLOYDIR}"
@@ -533,8 +499,7 @@  cache_dbg_pkgs() {
 
 ROOTFS_POSTPROCESS_COMMAND += "${@bb.utils.contains('ROOTFS_FEATURES', 'clean-package-cache', 'rootfs_postprocess_clean_package_cache', '', d)}"
 rootfs_postprocess_clean_package_cache() {
-    run_in_chroot '${ROOTFSDIR}' \
-        /usr/bin/apt-get clean
+    run_privileged ${HOST_APT_CMD} clean
     # remove apt-cache folder itself (required in case rootfs is provided by sstate cache)
     run_privileged find "${ROOTFSDIR}/var/cache/apt" -type f \
         \( -name '*.deb' -o -name '*.bin' \) -delete
diff --git a/meta/classes-recipe/sdk.bbclass b/meta/classes-recipe/sdk.bbclass
index ca38cfc3..eda1c92f 100644
--- a/meta/classes-recipe/sdk.bbclass
+++ b/meta/classes-recipe/sdk.bbclass
@@ -42,7 +42,10 @@  SDK_PREINSTALL += " \
     apt \
     automake \
     devscripts \
-    equivs"
+    equivs \
+    apt \
+    passwd \
+"
 
 # rootfs/image overrides for the SDK
 ROOTFS_ARCH:class-sdk = "${HOST_ARCH}"
diff --git a/meta/recipes-core/isar-mmdebstrap/isar-mmdebstrap.inc b/meta/recipes-core/isar-mmdebstrap/isar-mmdebstrap.inc
index ee6eda3c..cb8334eb 100644
--- a/meta/recipes-core/isar-mmdebstrap/isar-mmdebstrap.inc
+++ b/meta/recipes-core/isar-mmdebstrap/isar-mmdebstrap.inc
@@ -9,7 +9,7 @@  inherit bootstrap
 inherit compat
 inherit deb-dl-dir
 
-DISTRO_BOOTSTRAP_BASE_PACKAGES += "locales apt"
+DISTRO_BOOTSTRAP_BASE_PACKAGES += "locales"
 DISTRO_BOOTSTRAP_BASE_PACKAGES:append:https-support = " ca-certificates"
 
 BOOTSTRAP_TMPDIR = "${WORKDIR}/tempdir"
@@ -146,7 +146,7 @@  do_bootstrap() {
             bbfatal "${DISTRO_ARCH} does not have a compat arch"
         fi
     fi
-    bootstrap_args="--verbose --variant=minbase --include=${@','.join(d.getVar('DISTRO_BOOTSTRAP_BASE_PACKAGES').split())}"
+    bootstrap_args="--verbose --variant=essential --include=${@','.join(d.getVar('DISTRO_BOOTSTRAP_BASE_PACKAGES').split())}"
     if [ -f "${DISTRO_BOOTSTRAP_KEYRING}" ]; then
         bootstrap_args="$bootstrap_args --keyring=${DISTRO_BOOTSTRAP_KEYRING}"
         cp "${DISTRO_BOOTSTRAP_KEYRING}" "${WORKDIR}/trusted.gpg.d/"
@@ -174,10 +174,6 @@  do_bootstrap() {
         syncout='echo skip sync-out'
         extra_setup="mount --bind '${REPO_BASE_DIR}' $base_apt_tmp"
         extra_extract="$syncout"
-        # save mmdebstrap tempdir for cleanup
-        extra_essential="mkdir -p \$1/$base_apt_tmp && \
-                         echo \$1 > ${WORKDIR}/mmtmpdir && \
-                         mount -o bind,private '${REPO_BASE_DIR}' \$1/$base_apt_tmp"
         # replace base-apt mount in tmp with /base-apt mount
         extra_customize="sed -i \"s|copy://$base_apt_tmp|file:///base-apt|g\" \
                              \$1/etc/apt/sources.list.d/*.list && \
@@ -206,15 +202,6 @@  do_bootstrap() {
                        "${WORKDIR}/dl_dir/var/cache/apt/archives/"'
         extra_setup="$syncin"
         extra_extract="$syncout"
-        # prefetch apt debs because mmdebstrap will clean them on next stage
-        extra_essential='apt-get install apt -y -d \
-                                 -o Dir::State="$1/var/lib/apt" \
-                                 -o Dir::Etc="$1/etc/apt" \
-                                 -o Dir::Cache="$1/var/cache/apt" \
-                                 ${@'-o APT::Sandbox::User=root' if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else ''} \
-                                 -o Apt::Architecture="${BOOTSTRAP_DISTRO_ARCH}" \
-                                 ${@get_apt_opts(d, '-o')}'
-        extra_essential="$extra_essential && $syncout"
         extra_customize="$syncout"
     fi
 
@@ -250,11 +237,9 @@  do_bootstrap() {
                    --setup-hook='chown -R root:root "$1/etc/apt/trusted.gpg.d"' \
                    --setup-hook='install -v -m755 "${WORKDIR}/chroot-setup.sh" "$1/chroot-setup.sh"' \
                    --extract-hook="$extra_extract" \
-                   --essential-hook="$extra_essential" \
                    --customize-hook="$extra_customize" \
                    --customize-hook='sed -i "/en_US.UTF-8 UTF-8/s/^#//g" "$1/etc/locale.gen"' \
                    --customize-hook='chroot "$1" /usr/sbin/locale-gen' \
-                   --customize-hook='chroot "$1" /usr/bin/apt-get -y clean' \
                    --customize-hook='echo nameserver 127.0.0.1 > "$1"/etc/resolv.conf' \
                    --customize-hook='echo isar > "$1"/etc/hostname' \
                    ${@'--skip=output/dev' if d.getVar('ISAR_CHROOT_MODE') == 'unshare' else ''} \
diff --git a/meta/recipes-devtools/sbom-chroot/sbom-chroot.bb b/meta/recipes-devtools/sbom-chroot/sbom-chroot.bb
index f347327b..d0f848a8 100644
--- a/meta/recipes-devtools/sbom-chroot/sbom-chroot.bb
+++ b/meta/recipes-devtools/sbom-chroot/sbom-chroot.bb
@@ -21,7 +21,7 @@  DEPENDS:append:bookworm = " python3-cyclonedx-lib"
 DEPENDS:append:noble = " python3-cyclonedx-lib"
 DEPENDS += "python3-debsbom python3-spdx-tools"
 
-SBOM_IMAGE_INSTALL = "python3-debsbom python3-spdx-tools python3-cyclonedx-lib"
+SBOM_IMAGE_INSTALL = "python3-debsbom python3-spdx-tools python3-cyclonedx-lib lz4"
 
 ROOTFSDIR = "${WORKDIR}/rootfs"
 ROOTFS_PACKAGES = "${SBOM_IMAGE_INSTALL}"
diff --git a/meta/recipes-devtools/sbuild-chroot/sbuild-chroot.inc b/meta/recipes-devtools/sbuild-chroot/sbuild-chroot.inc
index 80c43d20..f0d79ad2 100644
--- a/meta/recipes-devtools/sbuild-chroot/sbuild-chroot.inc
+++ b/meta/recipes-devtools/sbuild-chroot/sbuild-chroot.inc
@@ -54,6 +54,7 @@  SBUILD_CHROOT_PREINSTALL_COMMON = " \
     ${@ 'ccache' if bb.utils.to_boolean(d.getVar('USE_CCACHE')) else ''} \
     devscripts \
     equivs \
+    apt \
 "
 
 SBUILD_CHROOT_DIR = "${WORKDIR}/rootfs"