Fix multi-arch builds to preserve OCI config metadata
All checks were successful
Build and Push Base Distro Images / build-and-push (apko/base.yaml, base) (push) Successful in 10m9s
Build and Push Base Distro Images / build-and-push (apko/build.yaml, build) (push) Successful in 10m20s
Build and Push Base Distro Images / build-and-push (apko/dotnet-runtime.yaml, dotnet-runtime) (push) Successful in 10m10s
Build and Push Base Distro Images / build-and-push (apko/dotnet-sdk.yaml, dotnet-sdk) (push) Successful in 10m18s
Build and Push Base Distro Images / build-and-push (apko/flutter-sdk.yaml, flutter-sdk) (push) Successful in 10m5s
Weekly Rebuild (CVE Updates) / rebuild (apko/base.yaml, base) (push) Successful in 9m56s
Weekly Rebuild (CVE Updates) / rebuild (apko/build.yaml, build) (push) Successful in 9m59s
Weekly Rebuild (CVE Updates) / rebuild (apko/dotnet-runtime.yaml, dotnet-runtime) (push) Successful in 9m59s
Weekly Rebuild (CVE Updates) / rebuild (apko/dotnet-sdk.yaml, dotnet-sdk) (push) Successful in 10m1s
Weekly Rebuild (CVE Updates) / rebuild (apko/flutter-sdk.yaml, flutter-sdk) (push) Successful in 10m0s

Extract rootfs layer and config from apko OCI archive separately,
then generate Dockerfile with ENV, ENTRYPOINT, WORKDIR and USER
from the OCI config. Fixes missing environment variables in the
final multi-arch image.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mathias Beaulieu-Duncan 2026-02-02 17:55:22 -05:00
parent 81b219fedc
commit 4120f820a0
2 changed files with 78 additions and 14 deletions

View File

@ -122,13 +122,45 @@ jobs:
apko build --arch aarch64 ${{ matrix.config }} \ apko build --arch aarch64 ${{ matrix.config }} \
${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-${{ steps.tag.outputs.suffix }} \ ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-${{ steps.tag.outputs.suffix }} \
/tmp/build-arm64/image.tar /tmp/build-arm64/image.tar
cat > /tmp/Dockerfile <<'EOF'
FROM scratch # Extract the rootfs layer from the OCI archive for each arch.
ARG TARGETARCH # apko outputs an OCI image tarball; we need to extract just the
ADD build-${TARGETARCH}/image.tar / # filesystem layer (.tar.gz) and the config metadata.
USER 65532 for arch in amd64 arm64; do
EOF cd /tmp/build-${arch}
sed -i 's/^ //' /tmp/Dockerfile tar xf image.tar
# The .tar.gz file is the filesystem layer
ROOTFS=$(ls *.tar.gz 2>/dev/null | head -1)
mv "$ROOTFS" rootfs.tar.gz
# Extract ENV and ENTRYPOINT from the OCI config
MANIFEST_DIGEST=$(jq -r '.manifests[0].digest' index.json | sed 's/sha256://')
CONFIG_DIGEST=$(jq -r '.config.digest' "sha256:${MANIFEST_DIGEST}" | sed 's/sha256://')
cp "sha256:${CONFIG_DIGEST}" config.json
# Clean up OCI artifacts
rm -f image.tar index.json manifest.json oci-layout sha256:*
cd /tmp
done
# Generate Dockerfile with metadata from the OCI config
# (use amd64 config as reference — env vars are the same for both arches)
ENV_LINES=$(jq -r '(.config.Env // [])[] | "ENV " + .' /tmp/build-amd64/config.json)
ENTRYPOINT=$(jq -r '(.config.Entrypoint // [])[]' /tmp/build-amd64/config.json | head -1)
USER_ID=$(jq -r '.config.User // "65532"' /tmp/build-amd64/config.json)
WORKDIR=$(jq -r '.config.WorkingDir // "/"' /tmp/build-amd64/config.json)
{
echo "FROM scratch"
echo "ARG TARGETARCH"
echo "ADD build-\${TARGETARCH}/rootfs.tar.gz /"
if [ -n "$ENV_LINES" ]; then
echo "$ENV_LINES"
fi
if [ -n "$ENTRYPOINT" ] && [ "$ENTRYPOINT" != "null" ]; then
echo "ENTRYPOINT [\"${ENTRYPOINT}\"]"
fi
echo "WORKDIR ${WORKDIR}"
echo "USER ${USER_ID}"
} > /tmp/Dockerfile
- name: Build and push with buildx (SBOM + provenance) - name: Build and push with buildx (SBOM + provenance)
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5

View File

@ -114,13 +114,45 @@ jobs:
apko build --arch aarch64 ${{ matrix.config }} \ apko build --arch aarch64 ${{ matrix.config }} \
${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-latest \ ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-latest \
/tmp/build-arm64/image.tar /tmp/build-arm64/image.tar
cat > /tmp/Dockerfile <<'EOF'
FROM scratch # Extract the rootfs layer from the OCI archive for each arch.
ARG TARGETARCH # apko outputs an OCI image tarball; we need to extract just the
ADD build-${TARGETARCH}/image.tar / # filesystem layer (.tar.gz) and the config metadata.
USER 65532 for arch in amd64 arm64; do
EOF cd /tmp/build-${arch}
sed -i 's/^ //' /tmp/Dockerfile tar xf image.tar
# The .tar.gz file is the filesystem layer
ROOTFS=$(ls *.tar.gz 2>/dev/null | head -1)
mv "$ROOTFS" rootfs.tar.gz
# Extract ENV and ENTRYPOINT from the OCI config
MANIFEST_DIGEST=$(jq -r '.manifests[0].digest' index.json | sed 's/sha256://')
CONFIG_DIGEST=$(jq -r '.config.digest' "sha256:${MANIFEST_DIGEST}" | sed 's/sha256://')
cp "sha256:${CONFIG_DIGEST}" config.json
# Clean up OCI artifacts
rm -f image.tar index.json manifest.json oci-layout sha256:*
cd /tmp
done
# Generate Dockerfile with metadata from the OCI config
# (use amd64 config as reference — env vars are the same for both arches)
ENV_LINES=$(jq -r '(.config.Env // [])[] | "ENV " + .' /tmp/build-amd64/config.json)
ENTRYPOINT=$(jq -r '(.config.Entrypoint // [])[]' /tmp/build-amd64/config.json | head -1)
USER_ID=$(jq -r '.config.User // "65532"' /tmp/build-amd64/config.json)
WORKDIR=$(jq -r '.config.WorkingDir // "/"' /tmp/build-amd64/config.json)
{
echo "FROM scratch"
echo "ARG TARGETARCH"
echo "ADD build-\${TARGETARCH}/rootfs.tar.gz /"
if [ -n "$ENV_LINES" ]; then
echo "$ENV_LINES"
fi
if [ -n "$ENTRYPOINT" ] && [ "$ENTRYPOINT" != "null" ]; then
echo "ENTRYPOINT [\"${ENTRYPOINT}\"]"
fi
echo "WORKDIR ${WORKDIR}"
echo "USER ${USER_ID}"
} > /tmp/Dockerfile
- name: Build and push with buildx (SBOM + provenance) - name: Build and push with buildx (SBOM + provenance)
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5