Files
monok8s/docs/ask/ask-deepresearch.md
2026-05-01 01:39:04 +08:00

27 KiB
Raw Blame History

Reproducible Container Build System for ASK Tarball Sources

Executive summary

The supplied ASK archive is buildable in containers, but in its current form it is not hermetic or reproducible enough for serious CI use. Inspection of the archive shows a Debian-oriented arm64 build that still fetches sources during the build, assumes a GNU/glibc cross toolchain, and expects a separate kernel tree for module builds. That combination is fine for local hacking and bad for supply-chain control.

The clean fix is straightforward: close the build over a packages/ directory that contains every source archive the build needs, pass only archive paths as build arguments, verify checksums, replace every git clone and wget step with tarball extraction plus patch application, and use a multi-stage Dockerfile that exports only artifacts. Dockers own docs explicitly support build arguments for parameterizing instructions, recommend multi-stage builds to keep final outputs small, and provide local exporters so you can pull files out of a build without shipping the entire toolchain image. citeturn9view0turn9view3turn9view5turn9view6turn23view0

The bad news is that a pure Alpine conversion is not free. Alpine uses musl, not glibc, and that changes ABI and runtime behavior. If the final ASK userspace binaries must run unchanged on a glibc-based target, Alpine-only is the wrong default unless you deliberately introduce a glibc sysroot, static-link what you can, or use compatibility mechanisms such as gcompat for simple cases. Alpines own docs and the musl docs both make that point indirectly but unmistakably. citeturn13view2turn13view3turn14view0turn13view1turn13view0

What the supplied ASK archive implies

Inspection of the supplied ASK tarball shows a C and kernel-module build for entity["company","NXP","semiconductor company"] Layerscape hardware. The root Makefile already pins fmlib and fmc to lf-6.12.49-2.2.0, downloads libnfnetlink-1.0.2 and libnetfilter_conntrack-1.1.0 during the build, and assumes a separate kernel source tree through KDIR. The included build/setup.sh is Debian host bootstrap logic; inside a container, that script should be retired, not executed.

For Alpine, the dependency picture is mixed but manageable. Official Alpine packages exist for libmnl-dev, libpcap-dev, libxml2-dev, and tclap-dev, with tclap-dev living in community. The nuisance dependency is libcli: Alpines package index only shows libcli in edge/testing, not as a stable v3.22 package, while upstream libclis own README documents a plain make && make install flow into /usr/local/lib. For this project, vendoring libcli as a tarball and building it in a helper stage is the sane choice; enabling edge/testing for one leaf dependency is not. citeturn4view0turn4view1turn14view2turn25search0turn19view0turn24view0turn26search10

There is one more important project-specific constraint: the archive does not include the kernel tree that ASKs out-of-tree modules need. So a fully reproducible module build requires one more input archive, such as packages/linux.tar.xz, or a deliberate decision to build only userspace.

The right shape is a closed build context. Put the ASK tarball, every required dependency tarball, and optionally the matching kernel source tarball in packages/. Keep the Docker context small with .dockerignore. Verify those archives with SHA256SUMS. Set SOURCE_DATE_EPOCH so timestamps stop drifting. In CI, pin the base image by digest instead of trusting a moving tag. Dockers best-practices guide is blunt about why: tags move, digests do not, multi-stage builds slim outputs, and .dockerignore matters. The SOURCE_DATE_EPOCH spec exists precisely to keep timestamps from poisoning reproducibility. citeturn23view0turn22search2turn22search4turn22search1

For ASK specifically, the least painful Alpine route is to build natively for linux/arm64 with Buildx rather than trying to recreate Debians crossbuild-essential-arm64 model inside Alpine. Debian has a purpose-built GNU/glibc cross meta-package; Alpines stable equivalent is not that. Alpines native toolchain story is build-base on musl, and Dockers multi-platform build support is exactly what makes --platform=linux/arm64 practical here. citeturn8view7turn13view4turn14view5turn9view4

flowchart TD
    A[packages/*.tar.* in build context] --> B[buildx builder stage]
    B --> C[extract ASK tarball]
    B --> D[extract vendored dependency tarballs]
    D --> E[patch and build fmlib, fmc, libnfnetlink, libnetfilter_conntrack, libcli]
    C --> F[build ASK userspace and optionally kernel modules]
    E --> F
    F --> G[dist or artifact directory]
    G --> H[scratch artifacts stage]
    H --> I[buildx local exporter to out/ask]

Concrete implementation

The wrapper below fixes the syntax problem in the prompt. docker buildx build needs an explicit build context, repeated --build-arg KEY=VALUE flags, and a --file argument. The local exporter is the right default here because ASKs real deliverable is a directory of build artifacts, not a fat runtime image. citeturn9view5turn9view6

Recommended .dockerignore

**
!Makefile
!docker/**
!packages/**

Docker recommends using .dockerignore to exclude irrelevant files from the build context, and that matters here because the whole point is to build from a tightly controlled set of source tarballs. citeturn23view0

Host-side vendoring script

#!/usr/bin/env sh
set -eu

PACKAGES_DIR="${1:-packages}"
ASK_SRC="${ASK_SRC:-/absolute/path/to/ASK.tar.gz}"
NXP_TAG="lf-6.12.49-2.2.0"

mkdir -p "${PACKAGES_DIR}"

install -m 0644 "${ASK_SRC}" "${PACKAGES_DIR}/ASK.tar.gz"

curl -L --fail -o "${PACKAGES_DIR}/fmlib-${NXP_TAG}.tar.gz" \
  "https://github.com/nxp-qoriq/fmlib/archive/refs/tags/${NXP_TAG}.tar.gz"

curl -L --fail -o "${PACKAGES_DIR}/fmc-${NXP_TAG}.tar.gz" \
  "https://github.com/nxp-qoriq/fmc/archive/refs/tags/${NXP_TAG}.tar.gz"

curl -L --fail -o "${PACKAGES_DIR}/libnfnetlink-1.0.2.tar.bz2" \
  "https://www.netfilter.org/projects/libnfnetlink/files/libnfnetlink-1.0.2.tar.bz2"

curl -L --fail -o "${PACKAGES_DIR}/libnetfilter_conntrack-1.1.0.tar.xz" \
  "https://www.netfilter.org/projects/libnetfilter_conntrack/files/libnetfilter_conntrack-1.1.0.tar.xz"

curl -L --fail -o "${PACKAGES_DIR}/libcli-1.10.7.tar.gz" \
  "https://github.com/dparrish/libcli/archive/refs/tags/V1.10.7.tar.gz"

# Optional: add a matching kernel source archive for full module builds.
# install -m 0644 /path/to/linux.tar.xz "${PACKAGES_DIR}/linux.tar.xz"

(
  cd "${PACKAGES_DIR}"
  find . -maxdepth 1 -type f \
    \( -name '*.tar.gz' -o -name '*.tar.xz' -o -name '*.tar.bz2' \) \
    -print0 | sort -z | xargs -0 sha256sum > SHA256SUMS
)

If local repacking is part of your process, normalize ownership, ordering, and mtimes and set SOURCE_DATE_EPOCH; otherwise you are leaking host-local timestamps into your supposedly reproducible input archive. citeturn22search1turn22search4

Root wrapper Makefile

.RECIPEPREFIX := >

PLATFORM ?= linux/arm64
ALPINE_VERSION ?= 3.22
BUILD_TARGET ?= dist
OUT_DIR ?= out/ask
IMAGE ?= ask-build:local

ASK_TAR ?= packages/ASK.tar.gz
FMLIB_TAR ?= packages/fmlib-lf-6.12.49-2.2.0.tar.gz
FMC_TAR ?= packages/fmc-lf-6.12.49-2.2.0.tar.gz
LIBNFNETLINK_TAR ?= packages/libnfnetlink-1.0.2.tar.bz2
LIBNFCT_TAR ?= packages/libnetfilter_conntrack-1.1.0.tar.xz
LIBCLI_TAR ?= packages/libcli-1.10.7.tar.gz
KERNEL_TAR ?= packages/linux.tar.xz

SOURCE_DATE_EPOCH ?= 1704067200
JOBS ?= 0
USERSPACE_CFLAGS ?=
USERSPACE_LDFLAGS ?=

.PHONY: ASK ASK_IMAGE

ASK:
> docker buildx build \
>   --platform="$(PLATFORM)" \
>   --file docker/ask.Dockerfile \
>   --build-arg "ALPINE_VERSION=$(ALPINE_VERSION)" \
>   --build-arg "ASK_TAR=$(ASK_TAR)" \
>   --build-arg "FMLIB_TAR=$(FMLIB_TAR)" \
>   --build-arg "FMC_TAR=$(FMC_TAR)" \
>   --build-arg "LIBNFNETLINK_TAR=$(LIBNFNETLINK_TAR)" \
>   --build-arg "LIBNFCT_TAR=$(LIBNFCT_TAR)" \
>   --build-arg "LIBCLI_TAR=$(LIBCLI_TAR)" \
>   --build-arg "KERNEL_TAR=$(KERNEL_TAR)" \
>   --build-arg "BUILD_TARGET=$(BUILD_TARGET)" \
>   --build-arg "SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH)" \
>   --build-arg "JOBS=$(JOBS)" \
>   --build-arg "USERSPACE_CFLAGS=$(USERSPACE_CFLAGS)" \
>   --build-arg "USERSPACE_LDFLAGS=$(USERSPACE_LDFLAGS)" \
>   --target artifacts \
>   --output "type=local,dest=$(OUT_DIR)" \
>   .

ASK_IMAGE:
> docker buildx build \
>   --platform="$(PLATFORM)" \
>   --file docker/ask.Dockerfile \
>   --build-arg "ALPINE_VERSION=$(ALPINE_VERSION)" \
>   --build-arg "ASK_TAR=$(ASK_TAR)" \
>   --build-arg "FMLIB_TAR=$(FMLIB_TAR)" \
>   --build-arg "FMC_TAR=$(FMC_TAR)" \
>   --build-arg "LIBNFNETLINK_TAR=$(LIBNFNETLINK_TAR)" \
>   --build-arg "LIBNFCT_TAR=$(LIBNFCT_TAR)" \
>   --build-arg "LIBCLI_TAR=$(LIBCLI_TAR)" \
>   --build-arg "KERNEL_TAR=$(KERNEL_TAR)" \
>   --build-arg "BUILD_TARGET=$(BUILD_TARGET)" \
>   --build-arg "SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH)" \
>   --build-arg "JOBS=$(JOBS)" \
>   --build-arg "USERSPACE_CFLAGS=$(USERSPACE_CFLAGS)" \
>   --build-arg "USERSPACE_LDFLAGS=$(USERSPACE_LDFLAGS)" \
>   --load \
>   --tag "$(IMAGE)" \
>   .

docker/overrides/toolchain.mk

CROSS_COMPILE ?=
ARCH ?= arm64
PLATFORM ?= LS1043A

CC ?= $(if $(CROSS_COMPILE),$(CROSS_COMPILE)gcc,gcc)
CXX ?= $(if $(CROSS_COMPILE),$(CROSS_COMPILE)g++,g++)
AR ?= $(if $(CROSS_COMPILE),$(CROSS_COMPILE)ar,ar)
STRIP ?= $(if $(CROSS_COMPILE),$(CROSS_COMPILE)strip,strip)

HOST ?= $(shell $(CC) -dumpmachine 2>/dev/null || echo aarch64-alpine-linux-musl)
KDIR ?= /opt/kernel

docker/overrides/Makefile

This override intentionally removes the upstream host-setup/network-fetch behavior and keeps only the build targets that matter for containerized CI.

.RECIPEPREFIX := >

include build/toolchain.mk
include build/sources.mk

DIST := $(CURDIR)/dist
SRCDIR := $(CURDIR)/sources
PATCHES := $(CURDIR)/patches
VENDOR_DIR ?= /vendor/packages
HOST ?= $(shell $(CC) -dumpmachine 2>/dev/null || echo aarch64-alpine-linux-musl)
JOBS ?= $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)

USERSPACE_CFLAGS ?=
USERSPACE_LDFLAGS ?=

FMLIB_DIR  := $(SRCDIR)/fmlib
FMC_DIR    := $(SRCDIR)/fmc/source
LIBFCI_DIR := $(CURDIR)/fci/lib
SYSROOT    := $(SRCDIR)/sysroot
ABM_DIR    := $(CURDIR)/auto_bridge

FMLIB_TAR ?= $(VENDOR_DIR)/fmlib-$(NXP_TAG).tar.gz
FMC_TAR ?= $(VENDOR_DIR)/fmc-$(NXP_TAG).tar.gz
LIBNFNETLINK_TAR ?= $(VENDOR_DIR)/libnfnetlink-$(LIBNFNETLINK_VER).tar.bz2
LIBNFCT_TAR ?= $(VENDOR_DIR)/libnetfilter_conntrack-$(LIBNFCT_VER).tar.xz

KBUILD_ARGS := CROSS_COMPILE=$(CROSS_COMPILE) ARCH=$(ARCH)
CDX_ARGS    := $(KBUILD_ARGS) KERNELDIR=$(KDIR) PLATFORM=$(PLATFORM)
FCI_ARGS    := $(KBUILD_ARGS) KERNEL_SOURCE=$(KDIR) BOARD_ARCH=$(ARCH) \
               KBUILD_EXTRA_SYMBOLS=$(CURDIR)/cdx/Module.symvers
ABM_ARGS    := $(KBUILD_ARGS) KERNEL_SOURCE=$(KDIR) PLATFORM=$(PLATFORM)

S := $(SRCDIR)/.stamps
$(shell mkdir -p $(S))

define extract_strip1
rm -rf $(2) && mkdir -p $(2) && tar -xf $(1) --strip-components=1 -C $(2)
endef

.PHONY: all sources modules userspace cdx fci auto_bridge fmc cmm dpa_app dist clean clean-all

all: modules userspace
sources: $(S)/fmlib $(S)/fmc $(S)/libfci $(S)/libnfnetlink $(S)/libnfct

$(S)/fmlib:
> @echo "==> fmlib: extract, patch, build"
> test -f $(FMLIB_TAR)
> $(call extract_strip1,$(FMLIB_TAR),$(FMLIB_DIR))
> cd $(FMLIB_DIR) && patch -p1 -i $(PATCHES)/fmlib/01-mono-ask-extensions.patch
> $(MAKE) -j$(JOBS) -C $(FMLIB_DIR) CROSS_COMPILE=$(CROSS_COMPILE) KERNEL_SRC=$(KDIR) libfm-arm.a
> ln -sf libfm-arm.a $(FMLIB_DIR)/libfm.a
> touch $@

$(S)/fmc:
> @echo "==> fmc: extract, patch, build"
> test -f $(FMC_TAR)
> rm -rf $(SRCDIR)/fmc
> mkdir -p $(SRCDIR)/fmc
> tar -xf $(FMC_TAR) -C $(SRCDIR)/fmc --strip-components=1
> cd $(FMC_DIR) && patch -p1 -i $(PATCHES)/fmc/01-mono-ask-extensions.patch
> $(MAKE) -j$(JOBS) -C $(FMC_DIR) \
>   CC=$(CC) \
>   CFLAGS="$(USERSPACE_CFLAGS)" \
>   LDFLAGS="$(USERSPACE_LDFLAGS)" \
>   TCLAP_HEADER_PATH=/usr/include \
>   LIBXML2_HEADER_PATH=/usr/include/libxml2 \
>   SYSROOT=$(SYSROOT)
> touch $@

$(S)/libnfnetlink:
> @echo "==> libnfnetlink: extract, patch, install into sysroot"
> test -f $(LIBNFNETLINK_TAR)
> rm -rf $(SRCDIR)/libnfnetlink-$(LIBNFNETLINK_VER)
> mkdir -p $(SRCDIR) $(SYSROOT)
> tar -xf $(LIBNFNETLINK_TAR) -C $(SRCDIR)
> cd $(SRCDIR)/libnfnetlink-$(LIBNFNETLINK_VER) && \
>   patch -p1 -i $(PATCHES)/libnfnetlink/0001-libnfnetlink-fix-for-ARM64-and-clang.patch && \
>   ./configure --host=$(HOST) --prefix=$(SYSROOT) --disable-shared --enable-static && \
>   $(MAKE) -j$(JOBS) && $(MAKE) install
> touch $@

$(S)/libnfct: $(S)/libnfnetlink
> @echo "==> libnetfilter_conntrack: extract, patch, install into sysroot"
> test -f $(LIBNFCT_TAR)
> rm -rf $(SRCDIR)/libnetfilter_conntrack-$(LIBNFCT_VER)
> mkdir -p $(SRCDIR) $(SYSROOT)
> tar -xf $(LIBNFCT_TAR) -C $(SRCDIR)
> cd $(SRCDIR)/libnetfilter_conntrack-$(LIBNFCT_VER) && \
>   patch -p1 -i $(PATCHES)/libnetfilter_conntrack/0001-libnetfilter_conntrack-fix-for-ARM64-and-clang.patch && \
>   ./configure --host=$(HOST) --prefix=$(SYSROOT) --with-libnfnetlink=$(SYSROOT) --disable-shared --enable-static && \
>   $(MAKE) -j$(JOBS) && $(MAKE) install
> touch $@

$(S)/libfci: $(S)/fmlib $(S)/libnfnetlink $(S)/libnfct
> @echo "==> libfci"
> $(MAKE) -C fci/lib CC=$(CC) AR=$(AR)
> touch $@

modules: cdx fci auto_bridge
userspace: fmc cmm dpa_app

cdx:
> $(MAKE) -C cdx $(CDX_ARGS)

fci: $(S)/libfci
> $(MAKE) -C fci $(FCI_ARGS)

auto_bridge:
> $(MAKE) -C auto_bridge $(ABM_ARGS)

fmc: $(S)/fmc

cmm: $(S)/libfci $(S)/libnfnetlink $(S)/libnfct
> $(MAKE) -C cmm \
>   CC=$(CC) \
>   LIBFCI_DIR=$(LIBFCI_DIR) \
>   ABM_DIR=$(ABM_DIR) \
>   SYSROOT=$(SYSROOT) \
>   CFLAGS="$(USERSPACE_CFLAGS)" \
>   LDFLAGS="$(USERSPACE_LDFLAGS)"

dpa_app: $(S)/fmc
> $(MAKE) -C dpa_app \
>   CC=$(CC) \
>   CFLAGS="-DDPAA_DEBUG_ENABLE -DNCSW_LINUX $(USERSPACE_CFLAGS) -I$(FMC_DIR) -I$(FMC_DIR)/inc/integrations/drivers/netcfg" \
>   LDFLAGS="-lpthread -lcli -lxml2 -lstdc++ $(USERSPACE_LDFLAGS)"

dist: all
> rm -rf $(DIST)
> mkdir -p $(DIST)
> cp -a $(CURDIR)/cmm/src/cmm $(DIST)/
> cp -a $(CURDIR)/dpa_app/dpa_app $(DIST)/
> cp -a $(CURDIR)/cdx/cdx.ko $(DIST)/
> cp -a $(CURDIR)/fci/fci.ko $(DIST)/
> cp -a $(CURDIR)/auto_bridge/auto_bridge.ko $(DIST)/
> cp -a $(SRCDIR)/fmc/source/fmc $(DIST)/
> cp -a scripts/init/* $(DIST)/

clean:
> $(MAKE) -C auto_bridge clean || true
> $(MAKE) -C cdx clean || true
> $(MAKE) -C cmm clean || true
> $(MAKE) -C dpa_app clean || true
> $(MAKE) -C fci clean || true
> $(MAKE) -C fci/lib clean || true
> rm -rf $(DIST)

clean-all: clean
> rm -rf $(SRCDIR)

docker/ask.Dockerfile

This Dockerfile deliberately enables Alpine community, because Alpine documents that community is not enabled by default in many configurations, and ASK needs tclap-dev, which is in that repository. citeturn26search10turn25search0

# syntax=docker/dockerfile:1.7

ARG ALPINE_VERSION=3.22

FROM alpine:${ALPINE_VERSION} AS builder
ARG ALPINE_VERSION

ARG ASK_TAR=packages/ASK.tar.gz
ARG FMLIB_TAR=packages/fmlib-lf-6.12.49-2.2.0.tar.gz
ARG FMC_TAR=packages/fmc-lf-6.12.49-2.2.0.tar.gz
ARG LIBNFNETLINK_TAR=packages/libnfnetlink-1.0.2.tar.bz2
ARG LIBNFCT_TAR=packages/libnetfilter_conntrack-1.1.0.tar.xz
ARG LIBCLI_TAR=packages/libcli-1.10.7.tar.gz
ARG KERNEL_TAR=packages/linux.tar.xz
ARG BUILD_TARGET=dist
ARG SOURCE_DATE_EPOCH=1704067200
ARG JOBS=0
ARG USERSPACE_CFLAGS=
ARG USERSPACE_LDFLAGS=

WORKDIR /work

RUN set -eux; \
    printf '%s\n' \
      "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/main" \
      "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/community" \
      > /etc/apk/repositories; \
    apk add --no-cache \
      bash \
      bc \
      bison \
      build-base \
      bzip2 \
      coreutils \
      file \
      findutils \
      flex \
      gawk \
      libmnl-dev \
      libpcap-dev \
      libxml2-dev \
      linux-headers \
      openssl \
      openssl-dev \
      patch \
      perl \
      pkgconf \
      python3 \
      tar \
      tclap-dev \
      xz \
      zlib-dev

COPY packages/ /vendor/packages/
COPY docker/overrides/ /docker-overrides/

RUN set -eux; \
    if [ -f /vendor/packages/SHA256SUMS ]; then \
      cd /vendor/packages && sha256sum -c SHA256SUMS; \
    fi

RUN set -eux; \
    test -f "/vendor/${ASK_TAR}"; \
    mkdir -p /work/src; \
    tar -xf "/vendor/${ASK_TAR}" -C /work/src; \
    test -d /work/src/ASK; \
    rm -rf /work/src/ASK/.git; \
    install -m 0644 /docker-overrides/Makefile /work/src/ASK/Makefile; \
    install -m 0644 /docker-overrides/toolchain.mk /work/src/ASK/build/toolchain.mk; \
    if [ ! -f /work/src/ASK/cmm/src/version.h ]; then \
      printf '/* Auto-generated */\n#ifndef VERSION_H\n#define VERSION_H\n#define CMM_VERSION "%s"\n#endif\n' "tarball" > /work/src/ASK/cmm/src/version.h; \
    fi

RUN set -eux; \
    test -f "/vendor/${LIBCLI_TAR}"; \
    mkdir -p /tmp/libcli; \
    tar -xf "/vendor/${LIBCLI_TAR}" --strip-components=1 -C /tmp/libcli; \
    make -C /tmp/libcli; \
    make -C /tmp/libcli install; \
    rm -rf /tmp/libcli

RUN set -eux; \
    case "${BUILD_TARGET}" in \
      all|modules|kernel|dist) \
        test -f "/vendor/${KERNEL_TAR}" || { echo "KERNEL_TAR is required for BUILD_TARGET=${BUILD_TARGET}"; exit 2; }; \
        mkdir -p /opt/kernel; \
        tar -xf "/vendor/${KERNEL_TAR}" --strip-components=1 -C /opt/kernel; \
        ;; \
      *) : ;; \
    esac

ENV LC_ALL=C
ENV TZ=UTC
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}

RUN set -eux; \
    if [ "${JOBS}" = "0" ]; then JOBS="$(getconf _NPROCESSORS_ONLN)"; fi; \
    make -C /work/src/ASK \
      VENDOR_DIR=/vendor/packages \
      FMLIB_TAR="/vendor/${FMLIB_TAR}" \
      FMC_TAR="/vendor/${FMC_TAR}" \
      LIBNFNETLINK_TAR="/vendor/${LIBNFNETLINK_TAR}" \
      LIBNFCT_TAR="/vendor/${LIBNFCT_TAR}" \
      KDIR=/opt/kernel \
      JOBS="${JOBS}" \
      USERSPACE_CFLAGS="${USERSPACE_CFLAGS}" \
      USERSPACE_LDFLAGS="${USERSPACE_LDFLAGS}" \
      "${BUILD_TARGET}"

RUN set -eux; \
    mkdir -p /out; \
    if [ -d /work/src/ASK/dist ]; then \
      cp -a /work/src/ASK/dist/. /out/; \
    else \
      [ -f /work/src/ASK/cmm/src/cmm ] && cp /work/src/ASK/cmm/src/cmm /out/ || true; \
      [ -f /work/src/ASK/dpa_app/dpa_app ] && cp /work/src/ASK/dpa_app/dpa_app /out/ || true; \
      [ -f /work/src/ASK/sources/fmc/source/fmc ] && cp /work/src/ASK/sources/fmc/source/fmc /out/ || true; \
      [ -f /work/src/ASK/cdx/cdx.ko ] && cp /work/src/ASK/cdx/cdx.ko /out/ || true; \
      [ -f /work/src/ASK/fci/fci.ko ] && cp /work/src/ASK/fci/fci.ko /out/ || true; \
      [ -f /work/src/ASK/auto_bridge/auto_bridge.ko ] && cp /work/src/ASK/auto_bridge/auto_bridge.ko /out/ || true; \
    fi

FROM scratch AS artifacts
COPY --from=builder /out/ /

The Dockerfile above uses COPY packages/ plus explicit tar -xf because that is the less magical pattern once you have multiple optional archives such as KERNEL_TAR, checksum verification, and tarballs whose extracted top-level directory names you do not want to trust blindly. Dockers docs are clear that ADD can auto-unpack local tar archives, but they are equally clear that COPY is the basic, explicit copy primitive. In practice, explicit wins here. citeturn23view0turn10view0turn10view1turn10view4

The normal invocation is make ASK BUILD_TARGET=dist KERNEL_TAR=packages/linux.tar.xz for a full module build, or make ASK BUILD_TARGET=userspace when only the source/dependency tarballs are available and the kernel tree is not. The artifact-export path is the better default; the image-loading target is only useful if you actually want a local OCI image as an intermediate. citeturn9view5turn9view6

Short ARG + ADD pattern and a secret-safe variant

If all you need is the shortest path-based pattern, Docker supports it:

ARG ASK_TAR=packages/ASK.tar.gz
ADD ${ASK_TAR} /work/src/

That works because local ADD sources are relative to the build context and local tar archives are unpacked automatically. But Docker also warns that build args are not secret channels. So if the archive is confidential, use a BuildKit secret mount instead:

# build command:
# docker buildx build --secret id=ask,src=packages/ASK.tar.gz -f docker/ask.Dockerfile .

RUN --mount=type=secret,id=ask,target=/tmp/ASK.tar.gz \
    mkdir -p /work/src && tar -xf /tmp/ASK.tar.gz -C /work/src

That is the secure answer. Build args should carry a path or selector, not secret bytes. citeturn10view0turn10view2turn9view0turn9view1

Debian and Alpine package mapping

The table below is the practical package/command map for the common build tools requested, plus the one ASK-specific exception that matters in real life.

Need Debian Trixie Alpine 3.22 Practical note
Meta toolchain bundle apt-get install -y build-essential apk add --no-cache build-base Rough equivalents for native builds
arm64 cross bundle apt-get install -y crossbuild-essential-arm64 no single stable equivalent On Alpine, use buildx --platform=linux/arm64 or bring your own GNU sysroot
C compiler apt-get install -y gcc apk add --no-cache gcc build-base already includes it
C++ compiler apt-get install -y g++ apk add --no-cache g++ build-base already includes it
make apt-get install -y make apk add --no-cache make build-base already includes it
CMake apt-get install -y cmake apk add --no-cache cmake Direct name match
pkg-config tooling apt-get install -y pkgconf apk add --no-cache pkgconf On Debian, pkg-config is now effectively the pkgconf world too
OpenSSL headers/libs apt-get install -y libssl-dev apk add --no-cache openssl-dev Direct development-package equivalents
zlib headers/libs apt-get install -y zlib1g-dev apk add --no-cache zlib-dev Direct development-package equivalents
C++ runtime runtime usually via libstdc++6; headers via g++/libstdc++-14-dev apk add --no-cache libstdc++ Alpine splits runtime from the compiler meta-package
libc headers apt-get install -y libc6-dev apk add --no-cache musl-dev Alpine exposes libc-dev through musl
ASK-specific TCLAP apt-get install -y libtclap-dev apk add --no-cache tclap-dev Needs Alpine community
ASK-specific libcli apt-get install -y libcli-dev vendor tarball Stable Alpine v3.22 does not have a normal libcli-dev package

These mappings are grounded in Debians package pages and Alpines official package index. The important details are: build-base bundles gcc, g++, make, patch, and libc-dev; musl-dev provides the libc-dev role on Alpine; pkgconf is the package you actually install for pkg-config tooling; and Debians crossbuild-essential-arm64 does not have a one-package Alpine twin. citeturn5search0turn8view7turn7view0turn8view0turn8view2turn5search1turn5search9turn8view3turn8view4turn8view5turn8view6turn14view5turn13view4turn4view2turn4view3turn14view1turn14view3turn14view4

For ASK specifically, the two Alpine deltas worth remembering are tclap-dev in community and the absence of stable libcli, which is why the helper-stage libcli build is worth doing. citeturn25search0turn19view0turn24view0turn26search10

Musl compatibility, troubleshooting, and assumptions

This is where most Alpine ports actually fail.

  • Do not trust glibc-specific #ifdef logic. The musl FAQ explicitly calls out hardcoded glibc assumptions and wrong __GLIBC__ checks as common failure causes. Fix the preprocessor logic first, not last. The same FAQ also calls out GNU getopt expectations, iconv/UCS2 assumptions, off_t width assumptions, and too-small default thread stacks as recurring causes of runtime failures. citeturn13view1

  • Musl locale behavior is not glibc locale behavior. Alpines own musl page says musl does not implement most of the locale features that glibc implements. If your code relies on glibc locale internals, stop pretending Alpine is a drop-in replacement. citeturn13view2

  • Plugin unload/reload semantics differ. musls dynamic loader keeps libraries loaded for the life of the process and treats dlclose as a no-op. If your software depends on glibc-style unload/reload behavior, that is a real portability bug, not a packaging bug. citeturn13view0

  • Static versus dynamic needs a hard-headed choice. -static-libgcc and -static-libstdc++ are good pragmatic flags when the problem is just the GCC/C++ runtime. They are not magic. Full static linking only works cleanly when every dependency is available as a static archive and your deployment model actually benefits from it.

  • gcompat is for simple runtime cases, not for wishful thinking. Alpine documents gcompat as a glibc compatibility layer for musl systems, and the package provides the relevant libc6-compat/loader compatibility pieces. For more complex glibc applications, Alpines own docs point you toward a glibc chroot/container instead. citeturn13view3turn14view0

  • ASK-specific practical fix: because ASKs source already contains at least one musl-friendly guard around execinfo/backtrace, a native Alpine build is plausible for the build itself. The unresolved question is not “can it compile?” It is “what libc must the resulting userspace binaries target on the final device?”

Where the upstream project is genuinely unspecified, the pattern stays the same and only the builder image family changes: C/C++/Make/CMake/autotools usually start from alpine, Go from golang:*-alpine, Python from python:*-alpine, and Node from node:*-alpine, all with the same multi-stage split. The exception rule is blunt: the moment the project depends on glibc-only binaries, ABI-sensitive vendor libraries, or packaging ecosystems that assume glibc, switch back to a glibc builder instead of burning time fighting musl. Dockers own guidance is to use trusted minimal images and multi-stage builds; Alpines own docs make clear that musl/glibc compatibility is extra engineering work, not something the base image solves for you. citeturn23view0turn9view3turn13view2turn13view3

Open questions / limitations

The supplied ASK tarball did not include the matching kernel source tree, so a fully reproducible module build still needs one more input archive. The deploy targets libc requirement was also unspecified; that matters a lot, because a musl-linked dpa_app or cmm is not automatically a drop-in replacement for a glibc target userland. Finally, no existing Debian Dockerfiles were present in the supplied archive, so the Alpine conversion above is a concrete replacement design derived from the archives real build logic, not a literal line-by-line rewrite of preexisting Dockerfiles.