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

36 KiB
Raw Blame History

Reproducible Docker Build System for a Tarball-Supplied Upstream Project

Executive summary

The right way to containerize a tarball-supplied upstream build is to make the build closed over the build context: put the upstream tarball and every third-party source archive in packages/, pass only paths as build arguments, verify checksums inside the build, extract archives explicitly, and remove every build-time git clone, wget, or other network fetch from the Dockerfile and the upstream Makefile. For the final output, use a multi-stage build and export only artifacts, not the entire toolchain image. Dockers official docs support exactly this pattern: build arguments to parameterize builds, multi-stage builds to shrink outputs, .dockerignore to keep the context tight, and the local exporter to write build artifacts directly to the host filesystem. citeturn9view6turn9view10turn15view0turn10view1turn9view12

For the supplied ASK archive specifically, direct inspection of the uploaded tarballs shows a C/C++ and kernel-module build for entity["company","NXP","semiconductor company"] Layerscape hardware, a Debian-oriented cross-build setup, hardcoded network fetches for fmlib, fmc, libnfnetlink, and libnetfilter_conntrack, and a separate kernel tree requirement through KDIR. The ASK tarball also contains a .git/ directory, so version generation must be normalized if you want reproducible tarball-based results. The supplied kernel archive expands as linux-lf-6.12.49-2.2.0/. Those facts drive the concrete implementation below.

The Alpine conversion is practical, but only if you treat musl seriously. Alpine uses musl, not glibc, and Alpines own docs warn that musl does not implement most glibc locale behavior. The musl docs also call out common failure modes: glibc-specific #ifdefs, GNU getopt expectations, iconv assumptions, small default thread stacks, and different dynamic-loader behavior. For simple glibc-runtime gaps, Alpine recommends gcompat; for harder runtime compatibility issues, Alpine explicitly points you to containers or chroots running a glibc distribution instead of pretending musl is a drop-in replacement. citeturn18view0turn18view1turn18view5turn18view7turn18view8

What the supplied tarballs imply

Direct inspection of the supplied ASK tarball shows all of the following.

The top-level Makefile includes build/toolchain.mk and build/sources.mk, sets HOST := aarch64-linux-gnu, defaults KDIR to $(HOME)/Mono/linux, and fetches upstream dependencies in the build itself. fmlib and fmc are cloned at tag lf-6.12.49-2.2.0; libnfnetlink-1.0.2 and libnetfilter_conntrack-1.1.0 are downloaded as tarballs and then patched. That is reproducibility-hostile because the build is not closed over the supplied source archive.

The provided ASK source already contains one musl-aware clue: cmm/src/cmm.c gates execinfo.h, backtrace(), and backtrace_symbols() behind __GLIBC__ checks. That means a pure musl build is plausible, but it does not prove the whole userspace is glibc-independent.

The ASK build is not just userspace. It also builds out-of-tree kernel modules (cdx, fci, auto_bridge), and those require a matching kernel source tree. So the build system needs a second tarball input for full module builds. If that kernel tarball is absent, the build should degrade cleanly to BUILD_TARGET=userspace.

There are no Dockerfiles in the supplied archive, so the Alpine conversion below is not a line-by-line rewrite of existing container files. It is a concrete replacement build design based on the actual source tree that was provided.

Assumptions where the upstream is unspecified

The concrete code below is tailored to the supplied ASK archive. Where the users request is broader than the archive, these are the assumptions:

  • The upstream source enters the build as packages/ASK.tar.gz.
  • Every third-party upstream source the build needs is also vendored into packages/.
  • Kernel module builds require a matching kernel source tarball, exposed as KERNEL_TAR.
  • If the project were not ASK but some other tarball-based upstream, the same pattern would still apply: replace in-build network fetches with tarball extraction; keep a closed build context; use a builder stage plus a minimal final stage.
  • If the language/build system were unspecified, a reasonable default is:
    • C/C++/make/cmake/autotools: Alpine builder with build-base and the needed -dev packages.
    • Go: Alpine builder with Go toolchain, then copy the compiled binary into a minimal runtime stage.
    • Python: Alpine builder with Python and build dependencies, then wheel install into a slim runtime stage.
    • Node: Alpine builder with Node toolchain, then copy built assets or production-only install into runtime.
  • No CPU/arch was specified in the prompt, but the supplied ASK sources are arm64-oriented, so the examples default to linux/arm64.

That base-image strategy follows Dockers guidance to use trusted minimal bases and multi-stage builds; the specific image family for Go, Python, or Node is a practical recommendation layered on top of that principle. citeturn15view3turn9view10

The design should be blunt and boring.

Put ASK.tar.gz, the kernel tarball, and every dependency tarball under packages/. Add a root .dockerignore that excludes everything else. Replace the upstream Makefiles git clone and wget logic with extraction from paths passed as build args. Verify SHA256SUMS in the builder stage. Remove .git after extraction or override the version-generation path so the build does not depend on VCS metadata. Set SOURCE_DATE_EPOCH, LC_ALL=C, and TZ=UTC so timestamps and locale-sensitive outputs stop drifting. Pin the base image by digest in CI, because Dockers docs are explicit that image tags are mutable; if you still permit BuildKit-managed remote source resolution, Docker also documents EXPERIMENTAL_BUILDKIT_SOURCE_POLICY for reproducible builds with pinned dependencies. citeturn15view0turn15view3turn10view4turn14search1turn10view5

For ASK, the best Alpine port is native Alpine build per target platform using docker buildx build --platform=linux/arm64, not a Debian-style crossbuild-essential-arm64 clone. Debian has an official cross meta-package for that workflow; Alpine stable does not give you the same one-shot cross package story, so Buildx plus platform selection is the cleaner solution here. Dockers buildx docs explicitly layer platform selection onto the whole Dockerfile, and Debians own package page makes clear what crossbuild-essential-arm64 actually is: a convenience list of cross-build essentials for Debian, not a universal pattern you must reproduce on Alpine. citeturn23view0turn23view1turn6view4

flowchart TD
    A[packages/ASK.tar.gz] --> B[builder stage]
    A2[packages/linux.tar.gz] --> B
    A3[vendored dependency tarballs] --> B
    B --> C[verify SHA256SUMS]
    C --> D[extract ASK tarball]
    D --> E[replace upstream fetch logic with tarball extraction]
    E --> F[build patched third-party deps]
    F --> G[build ASK userspace]
    G --> H{kernel tarball present?}
    H -->|yes| I[build kernel modules]
    H -->|no| J[skip modules and build userspace only]
    I --> K[stage dist artifacts]
    J --> K
    K --> L[scratch artifacts stage]
    L --> M[buildx local exporter writes out/ask]

Concrete implementation

Root .dockerignore

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

Host vendoring script

scripts/vendor-sources.sh

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

PACKAGES_DIR="${1:-packages}"
ASK_SRC="${ASK_SRC:-/absolute/path/to/ASK.tar.gz}"
KERNEL_SRC="${KERNEL_SRC:-/absolute/path/to/lf-6.12.49-2.2.0.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"
install -m 0644 "${KERNEL_SRC}" "${PACKAGES_DIR}/linux.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"

(
  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
)

Host-side extraction and normalization snippet

mkdir -p packages _inspect/ASK
cp /path/to/ASK.tar.gz packages/ASK.tar.gz
cp /path/to/lf-6.12.49-2.2.0.tar.gz packages/linux.tar.gz

tar -xf packages/ASK.tar.gz --strip-components=1 -C _inspect/ASK

export SOURCE_DATE_EPOCH=1704067200
tar --sort=name \
    --mtime="@${SOURCE_DATE_EPOCH}" \
    --owner=0 --group=0 --numeric-owner \
    -czf packages/ASK.normalized.tar.gz \
    -C _inspect ASK

Root Makefile

Makefile

.RECIPEPREFIX := >

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

ASK_TAR ?= packages/ASK.tar.gz
KERNEL_TAR ?= packages/linux.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

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

.PHONY: ASK ASK_IMAGE

ASK:
> docker buildx build \
>   --platform="$(DOCKER_PLATFORM)" \
>   --file docker/ask.Dockerfile \
>   --build-arg "ALPINE_VERSION=$(ALPINE_VERSION)" \
>   --build-arg "ASK_TAR=$(ASK_TAR)" \
>   --build-arg "KERNEL_TAR=$(KERNEL_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 "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="$(DOCKER_PLATFORM)" \
>   --file docker/ask.Dockerfile \
>   --build-arg "ALPINE_VERSION=$(ALPINE_VERSION)" \
>   --build-arg "ASK_TAR=$(ASK_TAR)" \
>   --build-arg "KERNEL_TAR=$(KERNEL_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 "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)" \
>   .

# Examples:
#   make ASK
#   make ASK BUILD_TARGET=userspace
#   make ASK DOCKER_PLATFORM=linux/amd64 BUILD_TARGET=userspace
#   make ASK USERSPACE_LDFLAGS="-static-libgcc -static-libstdc++"
#   make ASK ASK_TAR=packages/ASK.tar.gz KERNEL_TAR=packages/linux.tar.gz

The prompts example command is syntactically incomplete. The correct buildx form needs a Dockerfile specified with --file, one --build-arg KEY=VALUE flag per argument, and a final positional build context such as .. For artifact builds, --output type=local,dest=... is the right default; --load is only for a single-platform image result. citeturn9view6turn23view0turn10view0turn10view1

Upstream override files

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)

KDIR ?= /opt/kernel

docker/overrides/Makefile

.RECIPEPREFIX := >

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

DEFCONFIG  := $(CURDIR)/config/kernel/defconfig
DIST       := $(CURDIR)/dist
SRCDIR     := $(CURDIR)/sources
PATCHES    := $(CURDIR)/patches
HOST       ?= $(shell $(CC) -dumpmachine 2>/dev/null || echo aarch64-alpine-linux-musl)

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

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)

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

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

JOBS ?= 1
USERSPACE_CFLAGS ?=
USERSPACE_LDFLAGS ?=

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

all: modules userspace
setup:
> @echo "Container build: setup target intentionally disabled."

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

$(S)/fmlib:
> @echo "==> fmlib: extract + patch + build"
> rm -rf $(FMLIB_DIR)
> mkdir -p $(FMLIB_DIR)
> tar -xf "$(FMLIB_TAR)" --strip-components=1 -C $(FMLIB_DIR)
> cd $(FMLIB_DIR) && patch -p1 -i "$(PATCHES)/fmlib/01-mono-ask-extensions.patch"
> $(MAKE) -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: $(S)/fmlib
> @echo "==> fmc: extract + patch + build"
> rm -rf $(SRCDIR)/fmc
> mkdir -p $(SRCDIR)/fmc
> tar -xf "$(FMC_TAR)" --strip-components=1 -C $(SRCDIR)/fmc
> cd $(SRCDIR)/fmc && patch -p1 -i "$(PATCHES)/fmc/01-mono-ask-extensions.patch"
> $(MAKE) -C $(FMC_DIR) \
>   CC=$(CC) CXX=$(CXX) AR=$(AR) \
>   MACHINE=ls1046 \
>   FMD_USPACE_HEADER_PATH=$(FMLIB_DIR)/include/fmd \
>   FMD_USPACE_LIB_PATH=$(FMLIB_DIR) \
>   LIBXML2_HEADER_PATH=/usr/include/libxml2 \
>   TCLAP_HEADER_PATH=/usr/include
> touch $@

$(S)/libfci:
> @echo "==> libfci: build"
> $(MAKE) -C $(LIBFCI_DIR) CC=$(CC) AR=$(AR)
> touch $@

$(S)/libnfnetlink:
> @echo "==> libnfnetlink: extract + patch + build"
> 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/01-nxp-ask-nonblocking-heap-buffer.patch" && \
>   ./configure --host=$(HOST) --prefix=$(SYSROOT) --enable-static --disable-shared && \
>   $(MAKE) -j$(JOBS) && \
>   $(MAKE) install
> touch $@

$(S)/libnfct: $(S)/libnfnetlink
> @echo "==> libnetfilter_conntrack: extract + patch + build"
> 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/01-nxp-ask-comcerto-fp-extensions.patch" && \
>   PKG_CONFIG_PATH=$(SYSROOT)/lib/pkgconfig \
>   ./configure --host=$(HOST) --prefix=$(SYSROOT) --enable-static --disable-shared \
>     CFLAGS="-I$(SYSROOT)/include" LDFLAGS="-L$(SYSROOT)/lib" && \
>   $(MAKE) -j$(JOBS) && \
>   $(MAKE) install
> touch $@

modules: cdx fci auto_bridge

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

fci: cdx
> $(MAKE) -C fci $(FCI_ARGS) modules

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

userspace: fmc cmm dpa_app

fmc: $(S)/fmc
> @true

cmm: $(S)/libfci $(S)/libnfct
> $(MAKE) -C cmm CC=$(CC) \
>   LIBFCI_DIR=$(LIBFCI_DIR) \
>   ABM_DIR=$(ABM_DIR) \
>   SYSROOT=$(SYSROOT) \
>   CFLAGS="$(USERSPACE_CFLAGS) -I/usr/local/include" \
>   LDFLAGS="$(USERSPACE_LDFLAGS) -L/usr/local/lib"

dpa_app: $(S)/fmc
> $(MAKE) -C dpa_app CC=$(CC) \
>   CFLAGS="-DDPAA_DEBUG_ENABLE -DNCSW_LINUX $(USERSPACE_CFLAGS) \
>     -I/usr/local/include \
>     -I$(FMC_DIR) -I$(CURDIR)/cdx \
>     -I$(FMLIB_DIR)/include/fmd \
>     -I$(FMLIB_DIR)/include/fmd/Peripherals \
>     -I$(FMLIB_DIR)/include/fmd/integrations" \
>   LDFLAGS="-L/usr/local/lib -lpthread -lcli \
>     -L$(FMC_DIR) -lfmc \
>     -L$(FMLIB_DIR) -lfm \
>     -lstdc++ -lxml2 -lm $(USERSPACE_LDFLAGS)"

kernel:
> cp $(DEFCONFIG) $(KDIR)/.config
> $(MAKE) -C $(KDIR) $(KBUILD_ARGS) olddefconfig
> $(MAKE) -C $(KDIR) $(KBUILD_ARGS) -j$(JOBS) Image modules

dist: all
> mkdir -p $(DIST)
> cp cdx/cdx.ko $(DIST)/
> cp fci/fci.ko $(DIST)/
> cp auto_bridge/auto_bridge.ko $(DIST)/
> cp $(FMC_DIR)/fmc $(DIST)/
> cp cmm/src/cmm $(DIST)/
> cp dpa_app/dpa_app $(DIST)/
> @echo "Artifacts staged in $(DIST)/"

clean:
> -$(MAKE) -C cdx $(CDX_ARGS) clean
> -$(MAKE) -C fci $(FCI_ARGS) clean
> -$(MAKE) -C auto_bridge $(ABM_ARGS) clean
> -$(MAKE) -C $(LIBFCI_DIR) clean
> -$(MAKE) -C cmm clean
> -$(MAKE) -C dpa_app clean
> rm -f $(S)/*
> rm -rf $(DIST)

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

help:
> @echo "make               - build everything from vendored tarballs"
> @echo "make userspace     - build userspace only"
> @echo "make modules       - build out-of-tree kernel modules"
> @echo "make kernel        - build kernel Image + in-tree modules"
> @echo "make dist          - stage artifacts into dist/"
> @echo "make clean         - clean local build artifacts"
> @echo "make clean-all     - clean everything including extracted sources"

Alpine multi-stage Dockerfile

docker/ask.Dockerfile

# syntax=docker/dockerfile:1.7
#
# In CI, prefer pinning the base image by digest, for example:
# FROM alpine:3.22@sha256:<digest> AS base-build

ARG ALPINE_VERSION=3.22

FROM alpine:${ALPINE_VERSION} AS base-build
ARG ALPINE_VERSION

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-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

FROM base-build AS libcli-builder
ARG LIBCLI_TAR=packages/libcli-1.10.7.tar.gz

RUN set -eux; \
    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

FROM base-build AS builder
ARG ASK_TAR=packages/ASK.tar.gz
ARG KERNEL_TAR=packages/linux.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 BUILD_TARGET=dist
ARG SOURCE_DATE_EPOCH=1704067200
ARG JOBS=0
ARG USERSPACE_CFLAGS=
ARG USERSPACE_LDFLAGS=

COPY --from=libcli-builder /usr/local/ /usr/local/

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

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; \
    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

RUN set -eux; \
    if [ "${JOBS}" = "0" ]; then JOBS="$(getconf _NPROCESSORS_ONLN)"; fi; \
    make -C /work/src/ASK \
      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/ /

Example invocations

# Full ASK build, including modules, exporting files into out/ask/
make ASK

# Userspace-only build when the kernel tarball is unavailable
make ASK BUILD_TARGET=userspace

# Single-platform image load instead of local artifact export
make ASK_IMAGE BUILD_TARGET=userspace IMAGE=ask-build:dev

# Alpine userspace with selective static GCC/C++ runtime linkage
make ASK BUILD_TARGET=userspace \
  USERSPACE_LDFLAGS="-static-libgcc -static-libstdc++"

Sources and notes for the implementation above: .dockerignore behavior, Dockerfile-specific ignore precedence, build-context minimization, multi-stage builds, buildx build arguments, --output type=local, --load, --platform, and ADD/COPY semantics all come from Dockers official docs. The recommendation to use COPY for ordinary context files and reserve ADD for special cases is also straight from Dockers best-practices page. The Alpine repository split between main, community, and testing is official Alpine guidance, which matters here because tclap-dev lives in community. The libcli helper stage follows the upstream libcli README, which documents make and make install into /usr/local/lib. citeturn15view0turn15view1turn9view10turn10view0turn10view1turn10view3turn23view0turn23view1turn24view2turn24view1turn18view11turn1search1turn21search0

Debian-to-Alpine conversion guide

The important conversion is not just apt to apk. It is glibc-centric Debian cross-build assumptions to musl-centric Alpine native-per-target builds.

On Debian Trixie, build-essential and crossbuild-essential-arm64 are official convenience packages. build-essential pulls in the default GCC, G++, libc development headers, and make. crossbuild-essential-arm64 is Debians official one-shot cross-build convenience package for arm64. Alpines nearest equivalent for native builds is build-base, which explicitly depends on gcc, g++, make, and libc-dev; but Alpine does not provide an equivalent stable one-package story matching Debians arm64 cross meta-package. That is why the report recommends docker buildx build --platform=... rather than rebuilding Debians cross-toolchain pattern inside Alpine. citeturn4view0turn6view4turn2view0turn23view1

Alpine repository selection matters. Alpines official repositories are main, community, and testing; stable branches normally use main and community, while testing is edge-only and unsupported as a stable dependency source. That is directly relevant here because tclap-dev is in Alpine community, while the visible libcli package in Alpines official package index is in edge/testing rather than a normal stable -dev package flow. For a reproducible stable build, vendoring libcli as a tarball is cleaner than dragging edge/testing into a stable builder image. citeturn18view11turn1search1turn3view3

Debian and Alpine package mapping table

Capability Debian Trixie install command Alpine 3.22 install command Practical note
meta build toolchain apt-get update && apt-get install -y build-essential apk add --no-cache build-base Rough native-build equivalents
arm64 cross meta-package apt-get install -y crossbuild-essential-arm64 no stable one-package equivalent Prefer buildx --platform=linux/arm64 for Alpine
GCC C compiler apt-get install -y gcc apk add --no-cache gcc Alpine build-base already brings it in
GCC C++ compiler apt-get install -y g++ apk add --no-cache g++ Alpine build-base already brings it in
make apt-get install -y make apk add --no-cache make Alpine build-base already brings it in
CMake apt-get install -y cmake apk add --no-cache cmake Same user-facing package name
pkg-config tooling apt-get install -y pkgconf apk add --no-cache pkgconf pkgconf is the practical package on both sides
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 apt-get install -y libstdc++6 apk add --no-cache libstdc++ Runtime package names differ
libc development headers apt-get install -y libc6-dev apk add --no-cache musl-dev Alpine uses musl, not glibc
TCLAP headers apt-get install -y libtclap-dev apk add --no-cache tclap-dev Alpine package is in community
libcli development files apt-get install -y libcli-dev vendor the libcli tarball and build it Alpine stable does not give a clean libcli-dev replacement path

Sources and notes for the table: Debian package naming and dependency roles come from the official Debian package pages for build-essential, crossbuild-essential-arm64, pkgconf, libssl-dev, zlib1g-dev, libc6-dev, libcli-dev, g++, and the Trixie package index entry showing libtclap-dev. Alpine naming and repository placement come from the official Alpine package database pages for build-base, gcc, g++, make, cmake, pkgconf, openssl-dev, zlib-dev, libstdc++, musl-dev, tclap-dev, libpcap-dev, libmnl-dev, libxml2-dev, and the official libcli package page in edge/testing. citeturn4view0turn6view4turn4view1turn6view1turn6view2turn6view3turn6view0turn19view2turn5search0turn2view0turn1search17turn1search5turn2view1turn2view2turn2view3turn3view0turn3view1turn3view2turn1search13turn1search1turn6view6turn6view7turn6view8turn3view3

Passing tarballs safely and reproducibly

There are two different problems here, and they should not be confused.

If the tarball is not secret, the correct pattern is to pass a path selector as a build arg and copy the tarball into the build context. Dockers docs explicitly show --build-arg as the right mechanism for build-time parameterization, and the Dockerfile reference explicitly documents that local tar archives added with ADD are decompressed and extracted automatically. For normal reproducible CI, the cleaner pattern is still COPY packages/ ... plus explicit tar -xf, because it gives you better validation and less magic. citeturn9view1turn9view6turn24view2turn9view4turn9view5

If the tarball is secret or confidential, do not rely on ARG. Dockers docs warn that build args and environment variables are inappropriate for secrets and point you to secret mounts instead. That is the secure answer. citeturn9view0turn9view3turn16view1turn16view2

Non-secret build-arg pattern

ARG ASK_TAR=packages/ASK.tar.gz
COPY packages/ /vendor/packages/
RUN test -f "/vendor/${ASK_TAR}" && mkdir -p /src && tar -xf "/vendor/${ASK_TAR}" -C /src
ASK:
> docker buildx build \
>   --file docker/ask.Dockerfile \
>   --build-arg "ASK_TAR=$(ASK_TAR)" \
>   .

Short ADD pattern for a local tar archive

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

Secret-mount pattern for confidential tarballs

# syntax=docker/dockerfile:1.7
RUN --mount=type=secret,id=ask,target=/run/secrets/ASK.tar.gz \
    mkdir -p /src && \
    tar -xf /run/secrets/ASK.tar.gz -C /src
docker buildx build \
  --secret id=ask,src=packages/ASK.tar.gz \
  --file docker/ask.Dockerfile \
  .

Reproducibility controls worth enabling

Treat these as the minimum serious set:

  • Pin the base image by digest in CI.
  • Verify SHA256SUMS for every vendored tarball.
  • Set SOURCE_DATE_EPOCH.
  • Set LC_ALL=C and TZ=UTC.
  • Remove .git after extraction or replace VCS-derived version generation.
  • If any remote BuildKit source resolution remains, consider Dockers documented source policy feature.

The reason is simple: Docker tags are mutable, and SOURCE_DATE_EPOCH was created precisely so build systems can share a stable timestamp rather than leaking wall-clock time into artifacts. citeturn10view4turn15view3turn14search0turn14search1turn10view5

Musl troubleshooting and decision rules

Most Alpine ports do not fail because of package names. They fail because the code assumes glibc semantics.

Alpines own musl page says Alpine uses musl as its C standard library and that musl does not implement most locale features that glibc implements. The musl FAQ then gets more specific: common breakage comes from glibc-specific assumptions or wrong #ifdefs, GNU getopt behavior, iconv BOM and UCS2 assumptions, off_t width assumptions, and expectations that pthread_create gives you big glibc-sized stacks by default. That is not theory. Those are the actual usual failure modes. citeturn18view0turn18view1turn18view5turn18view6

For this ASK source tree, one obvious example is already handled: cmm.c only includes execinfo.h and uses backtrace() on glibc. That is the right pattern. If other source files still assume glibc-only headers or symbols, patch them the same way: guard them with #if defined(__GLIBC__), provide a musl-safe fallback, or compile out the diagnostic-only path.

Threading and loader behavior are the next big traps. The musl docs say the default thread stack is much smaller than glibcs and can be increased explicitly with pthread_attr_setstacksize, or, since newer musl, via -Wl,-z,stack-size=N. The musl functional-differences page also says dlclose() semantics differ materially: under musl, constructors run only once and destructors run on exit, not on each unload/reload cycle as many glibc-focused programs implicitly assume. If the upstream relies on library unloading or reinitialization as a runtime feature, that is not an Alpine packaging bug; it is a portability bug in the application. citeturn18view3turn18view2

Some glibc gaps are small enough for compatibility shims. Alpines official software-management page says that for simpler binaries you can install gcompat, which provides glibc-compatible APIs on musl systems. On aarch64, Alpines package contents show that gcompat provides the glibc-style loader name and a libc.so.6 compatibility path. That makes gcompat a legitimate option when the problem is a narrow runtime ABI expectation rather than deep glibc dependence. citeturn18view7turn18view8turn13search6

When gcompat is not enough, stop wasting time. Alpines own docs explicitly recommend containers or chroots for running glibc programs. If the upstream depends on glibc-only locale behavior, NSS/plugins, loader behavior, or opaque vendor binaries built for glibc, the pragmatic answer is a glibc builder or runtime stage for that component, even if the rest of your pipeline uses Alpine. citeturn18view8

Alpine-specific shell and utility differences also matter. Alpine is built around musl and BusyBox, and Alpines docs warn that BusyBox tools tend to implement only standard options and often lack GNU-specific extensions. If the upstream scripts assume GNU sed, GNU find, bash, or other non-POSIX behavior, install the needed packages explicitly or patch the scripts. The Dockerfile above does exactly that by installing bash, coreutils, findutils, gawk, and related tools. citeturn18view10

One more gotcha: musl does not implement utmp; Alpines docs say those functions are stubbed. If the upstream or its tests use wall, who, w, or similar libc-backed session accounting behavior, expect that to differ on Alpine. citeturn18view9

Practical fix list

Use these fixes in this order:

  • Wrong #ifdefs or missing glibc headers
    Patch to feature checks or __GLIBC__ guards. ASK already does this for execinfo.h/backtrace() in cmm.c.

  • Tiny musl thread stacks
    Patch the app to call pthread_attr_setstacksize, or pass a linker stack-size hint with -Wl,-z,stack-size=N when appropriate. citeturn18view3

  • Userspace portability while keeping dynamic linking
    Prefer normal musl dynamic linking first.

  • GCC runtime portability only
    Try -static-libgcc -static-libstdc++ first. This is often enough when the program is otherwise musl-clean but you want to reduce deployment friction around the GCC runtimes.

  • Simple glibc ABI gaps at runtime
    Try apk add gcompat. citeturn18view7turn18view8turn13search6

  • Deep glibc dependencies
    Use a glibc runtime image or Alpine-documented container/chroot strategy. citeturn18view8

  • Full static linking
    Use only if every dependency is available static and you have checked licensing and target runtime needs. Do not default to -static blindly.

flowchart TD
    A[Need Alpine runtime?] -->|No| B[Use Alpine builder only and export artifacts]
    A -->|Yes| C{Does the binary run correctly on musl?}
    C -->|Yes| D[Use Alpine runtime stage]
    C -->|Minor glibc ABI loader gap| E[Try gcompat]
    C -->|glibc-specific behavior or binary blob| F[Use glibc runtime image or chroot/container]

Open questions and limitations

The supplied archive did not contain any original Dockerfiles, so the Alpine design above is a concrete replacement architecture, not a textual translation of preexisting container files.

The full module build depends on a matching kernel source tarball. The uploaded kernel archive makes that possible here, but in a generic tarball-only scenario you must state that dependency explicitly rather than silently assuming a host kernel tree exists.

The final deployment environment for ASK userspace binaries was not specified. That matters. If the target root filesystem is glibc-based and you want drop-in userspace binaries, musl-built artifacts may not be the right end state even if the build itself succeeds.

The implementation above is intentionally strict: no in-build git clone, no wget, no unpinned hidden source fetches, no dependence on host package managers inside the source tree, and no ambiguity about whether the tarball or the network is the source of truth. That is the reproducible answer.