Initial base distro with apko/Wolfi configs

Five minimal OCI image variants built with apko:
- base: ~5.5MB glibc runtime (wolfi-baselayout, libstdc++, ca-certs, tzdata)
- build: base + build tools (bash, git, curl, wget, unzip, xz)
- dotnet-runtime: base + ICU, OpenSSL, zlib for .NET runtime
- dotnet-sdk: build + ICU, OpenSSL, zlib for .NET SDK
- flutter: build variant configured for Flutter SDK

Includes melange package definitions for .NET 10 SDK/runtime and
Flutter SDK (for future use when building custom APKs).

CI/CD pipelines: publish on release, Scout CVE comparison on PRs,
weekly rebuild for Wolfi security patches.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mathias Beaulieu-Duncan 2026-02-02 02:32:32 -05:00
commit 734939fd12
15 changed files with 677 additions and 0 deletions

View File

@ -0,0 +1,66 @@
name: Build and Push Base Distro Images
on:
release:
types: [published, prereleased]
workflow_dispatch:
permissions:
contents: read
env:
IMAGE_NAME: base-distro
jobs:
build-and-push:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- config: apko/base.yaml
variant: base
- config: apko/build.yaml
variant: build
- config: apko/dotnet-runtime.yaml
variant: dotnet-runtime
- config: apko/dotnet-sdk.yaml
variant: dotnet-sdk
- config: apko/flutter.yaml
variant: flutter
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Determine tag
id: tag
run: |
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
echo "suffix=dev" >> $GITHUB_OUTPUT
else
echo "suffix=latest" >> $GITHUB_OUTPUT
fi
- name: Install apko
run: |
curl -fsSL "https://github.com/chainguard-dev/apko/releases/latest/download/apko_$(uname -s)_$(uname -m).tar.gz" | tar xz -C /usr/local/bin apko
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push image
run: |
apko publish ${{ matrix.config }} \
${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-${{ steps.tag.outputs.suffix }}
- name: Install Docker Scout
run: |
curl -fsSL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh -o install-scout.sh
sh install-scout.sh
- name: Docker Scout CVE Scan
run: |
docker pull ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-${{ steps.tag.outputs.suffix }}
docker scout cves ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-${{ steps.tag.outputs.suffix }} --only-severity critical,high

View File

@ -0,0 +1,58 @@
name: Weekly Rebuild (CVE Updates)
on:
schedule:
# Rebuild weekly to pick up Wolfi security patches
- cron: '0 6 * * 1'
workflow_dispatch:
permissions:
contents: read
env:
IMAGE_NAME: base-distro
jobs:
rebuild:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- config: apko/base.yaml
variant: base
- config: apko/build.yaml
variant: build
- config: apko/dotnet-runtime.yaml
variant: dotnet-runtime
- config: apko/dotnet-sdk.yaml
variant: dotnet-sdk
- config: apko/flutter.yaml
variant: flutter
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install apko
run: |
curl -fsSL "https://github.com/chainguard-dev/apko/releases/latest/download/apko_$(uname -s)_$(uname -m).tar.gz" | tar xz -C /usr/local/bin apko
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Rebuild and push with latest Wolfi packages
run: |
apko publish ${{ matrix.config }} \
${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-latest
- name: Install Docker Scout
run: |
curl -fsSL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh -o install-scout.sh
sh install-scout.sh
- name: Docker Scout CVE Scan
run: |
docker pull ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-latest
docker scout cves ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-latest --only-severity critical,high

View File

@ -0,0 +1,72 @@
name: Docker Scout Analysis
on:
pull_request:
branches: ["**"]
permissions:
contents: read
pull-requests: write
env:
IMAGE_NAME: base-distro
jobs:
scout:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- config: apko/base.yaml
variant: base
- config: apko/build.yaml
variant: build
- config: apko/dotnet-runtime.yaml
variant: dotnet-runtime
- config: apko/dotnet-sdk.yaml
variant: dotnet-sdk
- config: apko/flutter.yaml
variant: flutter
steps:
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Check if latest image exists
id: should_run
run: |
if docker manifest inspect ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-latest > /dev/null 2>&1; then
echo "run=true" >> $GITHUB_OUTPUT
echo "${{ matrix.variant }}-latest found, Scout compare will run"
else
echo "run=false" >> $GITHUB_OUTPUT
echo "No ${{ matrix.variant }}-latest found, skipping"
fi
- name: Checkout code
if: steps.should_run.outputs.run == 'true'
uses: actions/checkout@v3
- name: Install apko
if: steps.should_run.outputs.run == 'true'
run: |
curl -fsSL "https://github.com/chainguard-dev/apko/releases/latest/download/apko_$(uname -s)_$(uname -m).tar.gz" | tar xz -C /usr/local/bin apko
- name: Build image locally
if: steps.should_run.outputs.run == 'true'
run: |
apko build ${{ matrix.config }} ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-pr-${{ github.event.pull_request.number }} ${{ matrix.variant }}.tar
docker load < ${{ matrix.variant }}.tar
- name: Install Docker Scout
if: steps.should_run.outputs.run == 'true'
run: |
curl -fsSL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh -o install-scout.sh
sh install-scout.sh
- name: Docker Scout Compare
if: steps.should_run.outputs.run == 'true'
run: |
docker scout compare ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-pr-${{ github.event.pull_request.number }} --to ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ matrix.variant }}-latest --ignore-unchanged --only-severity critical,high

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
*.tar
*.tar.gz
packages/
signing-key.*
!signing-key.rsa.pub.example
sbom-*.spdx.json

67
Makefile Normal file
View File

@ -0,0 +1,67 @@
REGISTRY ?= svrnty/base-distro
APKO_FLAGS ?= --log-level info
# Image variants
VARIANTS = base build dotnet-runtime dotnet-sdk flutter
.PHONY: all clean $(VARIANTS) test
all: $(VARIANTS)
# Build each variant with apko
base:
apko build $(APKO_FLAGS) apko/base.yaml $(REGISTRY):base base.tar
docker load < base.tar
@echo "Built $(REGISTRY):base"
build:
apko build $(APKO_FLAGS) apko/build.yaml $(REGISTRY):build build.tar
docker load < build.tar
@echo "Built $(REGISTRY):build"
dotnet-runtime:
apko build $(APKO_FLAGS) apko/dotnet-runtime.yaml $(REGISTRY):dotnet-runtime dotnet-runtime.tar
docker load < dotnet-runtime.tar
@echo "Built $(REGISTRY):dotnet-runtime"
dotnet-sdk:
apko build $(APKO_FLAGS) apko/dotnet-sdk.yaml $(REGISTRY):dotnet-sdk dotnet-sdk.tar
docker load < dotnet-sdk.tar
@echo "Built $(REGISTRY):dotnet-sdk"
flutter:
apko build $(APKO_FLAGS) apko/flutter.yaml $(REGISTRY):flutter flutter.tar
docker load < flutter.tar
@echo "Built $(REGISTRY):flutter"
# Test all images
test: all
@echo "=== Testing base ==="
docker run --rm $(REGISTRY):base /bin/sh -c "cat /etc/os-release"
@echo ""
@echo "=== Testing build ==="
docker run --rm $(REGISTRY):build bash -c "git --version && curl --version | head -1"
@echo ""
@echo "=== Testing dotnet-runtime ==="
docker run --rm $(REGISTRY):dotnet-runtime /bin/sh -c "ls /usr/lib/libicu*"
@echo ""
@echo "=== Testing dotnet-sdk ==="
docker run --rm $(REGISTRY):dotnet-sdk bash -c "git --version && ls /usr/lib/libicu*"
@echo ""
@echo "=== Testing flutter ==="
docker run --rm $(REGISTRY):flutter bash -c "git --version && echo PATH=\$$PATH"
@echo ""
@echo "All tests passed!"
# Show image sizes
sizes: all
@echo "=== Image Sizes ==="
@for variant in $(VARIANTS); do \
echo -n "$(REGISTRY):$$variant "; \
docker image inspect $(REGISTRY):$$variant --format '{{.Size}}' | numfmt --to=iec 2>/dev/null || \
docker image inspect $(REGISTRY):$$variant --format '{{.Size}}'; \
done
clean:
rm -f *.tar
rm -rf packages/

30
apko/base.yaml Normal file
View File

@ -0,0 +1,30 @@
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
- wolfi-baselayout
- glibc
- glibc-locale-posix
- libstdc++
- ca-certificates-bundle
- tzdata
- busybox
accounts:
groups:
- groupname: app
gid: 65532
users:
- username: app
uid: 65532
gid: 65532
run-as: 65532
archs:
- x86_64
- aarch64
environment:
TZ: UTC

42
apko/build.yaml Normal file
View File

@ -0,0 +1,42 @@
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
# Base runtime
- wolfi-baselayout
- glibc
- glibc-locale-posix
- libstdc++
- ca-certificates-bundle
- tzdata
# Build tools
- bash
- busybox
- coreutils
- git
- curl
- wget
- unzip
- xz
accounts:
groups:
- groupname: app
gid: 65532
users:
- username: app
uid: 65532
gid: 65532
run-as: 65532
archs:
- x86_64
- aarch64
environment:
TZ: UTC
entrypoint:
command: /bin/bash

37
apko/dotnet-runtime.yaml Normal file
View File

@ -0,0 +1,37 @@
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
# Base runtime
- wolfi-baselayout
- glibc
- glibc-locale-posix
- libstdc++
- ca-certificates-bundle
- tzdata
- busybox
# .NET runtime dependencies
- icu
- libssl3
- zlib
accounts:
groups:
- groupname: app
gid: 65532
users:
- username: app
uid: 65532
gid: 65532
run-as: 65532
archs:
- x86_64
- aarch64
environment:
TZ: UTC
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: "false"
DOTNET_RUNNING_IN_CONTAINER: "true"

49
apko/dotnet-sdk.yaml Normal file
View File

@ -0,0 +1,49 @@
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
# Base runtime
- wolfi-baselayout
- glibc
- glibc-locale-posix
- libstdc++
- ca-certificates-bundle
- tzdata
# .NET runtime dependencies
- icu
- libssl3
- zlib
# Build tools
- bash
- busybox
- coreutils
- git
- curl
- wget
- unzip
- xz
accounts:
groups:
- groupname: app
gid: 65532
users:
- username: app
uid: 65532
gid: 65532
run-as: 65532
archs:
- x86_64
- aarch64
environment:
TZ: UTC
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: "false"
DOTNET_RUNNING_IN_CONTAINER: "true"
DOTNET_CLI_TELEMETRY_OPTOUT: "true"
entrypoint:
command: /bin/bash

44
apko/flutter.yaml Normal file
View File

@ -0,0 +1,44 @@
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
# Base runtime
- wolfi-baselayout
- glibc
- glibc-locale-posix
- libstdc++
- ca-certificates-bundle
- tzdata
# Build tools
- bash
- busybox
- coreutils
- git
- curl
- wget
- unzip
- xz
accounts:
groups:
- groupname: flutter
gid: 65532
users:
- username: flutter
uid: 65532
gid: 65532
run-as: 65532
archs:
- x86_64
- aarch64
environment:
TZ: UTC
FLUTTER_HOME: /opt/flutter
PATH: /opt/flutter/bin:/opt/flutter/bin/cache/dart-sdk/bin:/usr/bin:/bin:/usr/sbin:/sbin
entrypoint:
command: /bin/bash

View File

@ -0,0 +1,43 @@
# Example: .NET 10 runtime image using base-distro
#
# Usage in accounting-api or route-api:
# FROM svrnty/base-distro:dotnet-sdk-latest AS build
# ... (build stage with .NET SDK installed on top) ...
#
# FROM svrnty/base-distro:dotnet-runtime-latest AS final
# COPY --from=build /app .
# ENTRYPOINT ["dotnet", "MyApp.dll"]
# Build stage: use the SDK base + install .NET SDK
FROM svrnty/base-distro:dotnet-sdk-latest AS build
# Install .NET 10 SDK (not yet in Wolfi, manual tarball install)
USER root
RUN curl -fsSL "https://dotnetcli.azureedge.net/dotnet/Sdk/10.0.100/dotnet-sdk-10.0.100-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tar.gz" \
-o /tmp/dotnet-sdk.tar.gz && \
mkdir -p /usr/share/dotnet && \
tar xf /tmp/dotnet-sdk.tar.gz -C /usr/share/dotnet && \
ln -sf /usr/share/dotnet/dotnet /usr/bin/dotnet && \
rm /tmp/dotnet-sdk.tar.gz
WORKDIR /source
COPY . .
RUN dotnet publish -o /app
# Runtime stage: minimal base + .NET runtime only
FROM svrnty/base-distro:dotnet-runtime-latest AS final
# Install .NET 10 ASP.NET runtime
USER root
RUN curl -fsSL "https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/10.0.0/aspnetcore-runtime-10.0.0-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tar.gz" \
-o /tmp/aspnet-runtime.tar.gz && \
mkdir -p /usr/share/dotnet && \
tar xf /tmp/aspnet-runtime.tar.gz -C /usr/share/dotnet && \
ln -sf /usr/share/dotnet/dotnet /usr/bin/dotnet && \
rm /tmp/aspnet-runtime.tar.gz
WORKDIR /app
COPY --from=build /app .
USER 65532
ENTRYPOINT ["dotnet", "MyApp.dll"]

View File

@ -0,0 +1,30 @@
# Example: Flutter web build image using base-distro
#
# Usage in flutter-admin-console or other Flutter web projects:
# FROM svrnty/base-distro:flutter-latest AS build
# ... (install Flutter SDK, build web app) ...
FROM svrnty/base-distro:flutter-latest AS build
# Install Flutter SDK on top of the base
USER root
ARG FLUTTER_VERSION=3.38.9
RUN curl -fsSL "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz" \
-o /tmp/flutter.tar.xz && \
mkdir -p /opt && \
tar xf /tmp/flutter.tar.xz -C /opt && \
rm /tmp/flutter.tar.xz && \
git config --global --add safe.directory /opt/flutter && \
flutter config --enable-web \
--no-enable-android --no-enable-ios \
--no-enable-linux-desktop --no-enable-macos-desktop \
--no-enable-windows-desktop && \
flutter precache --web \
--no-android --no-ios --no-linux \
--no-macos --no-windows --no-fuchsia --no-universal && \
chown -R 65532:65532 /opt/flutter
USER 65532
WORKDIR /app
COPY . .
RUN flutter pub get && flutter build web --wasm --release

View File

@ -0,0 +1,42 @@
package:
name: dotnet-10-runtime
version: 10.0.0
epoch: 0
description: ".NET 10 Runtime from official Microsoft binaries"
copyright:
- license: MIT
environment:
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
- wolfi-baselayout
- busybox
- curl
pipeline:
- runs: |
case "$(uname -m)" in
x86_64) ARCH="x64" ;;
aarch64) ARCH="arm64" ;;
*) echo "Unsupported arch: $(uname -m)" && exit 1 ;;
esac
DOTNET_VERSION="${{package.version}}"
# Download .NET runtime from Microsoft
curl -fsSL "https://dotnetcli.azureedge.net/dotnet/Runtime/${DOTNET_VERSION}/dotnet-runtime-${DOTNET_VERSION}-linux-${ARCH}.tar.gz" \
-o /tmp/dotnet-runtime.tar.gz
# Install to package destination
mkdir -p "${{targets.destdir}}/usr/share/dotnet"
tar xf /tmp/dotnet-runtime.tar.gz -C "${{targets.destdir}}/usr/share/dotnet"
# Create symlink
mkdir -p "${{targets.destdir}}/usr/bin"
ln -s /usr/share/dotnet/dotnet "${{targets.destdir}}/usr/bin/dotnet"
rm /tmp/dotnet-runtime.tar.gz

View File

@ -0,0 +1,42 @@
package:
name: dotnet-10-sdk
version: 10.0.100
epoch: 0
description: ".NET 10 SDK from official Microsoft binaries"
copyright:
- license: MIT
environment:
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
- wolfi-baselayout
- busybox
- curl
pipeline:
- runs: |
case "$(uname -m)" in
x86_64) ARCH="x64" ;;
aarch64) ARCH="arm64" ;;
*) echo "Unsupported arch: $(uname -m)" && exit 1 ;;
esac
SDK_VERSION="${{package.version}}"
# Download .NET SDK from Microsoft
curl -fsSL "https://dotnetcli.azureedge.net/dotnet/Sdk/${SDK_VERSION}/dotnet-sdk-${SDK_VERSION}-linux-${ARCH}.tar.gz" \
-o /tmp/dotnet-sdk.tar.gz
# Install to package destination
mkdir -p "${{targets.destdir}}/usr/share/dotnet"
tar xf /tmp/dotnet-sdk.tar.gz -C "${{targets.destdir}}/usr/share/dotnet"
# Create symlink
mkdir -p "${{targets.destdir}}/usr/bin"
ln -s /usr/share/dotnet/dotnet "${{targets.destdir}}/usr/bin/dotnet"
rm /tmp/dotnet-sdk.tar.gz

View File

@ -0,0 +1,48 @@
package:
name: flutter-sdk
version: 3.38.9
epoch: 0
description: "Flutter SDK for web/WASM builds"
copyright:
- license: BSD-3-Clause
environment:
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
- wolfi-baselayout
- busybox
- curl
- git
- xz
pipeline:
- runs: |
FLUTTER_VERSION="${{package.version}}"
# Download Flutter SDK tarball (linux x86_64 only for now)
curl -fsSL "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz" \
-o /tmp/flutter.tar.xz
# Install to package destination
mkdir -p "${{targets.destdir}}/opt"
tar xf /tmp/flutter.tar.xz -C "${{targets.destdir}}/opt"
# Mark git safe directory
git config --global --add safe.directory /opt/flutter
# Configure for web-only
"${{targets.destdir}}/opt/flutter/bin/flutter" config --enable-web \
--no-enable-android --no-enable-ios \
--no-enable-linux-desktop --no-enable-macos-desktop \
--no-enable-windows-desktop
# Precache web artifacts
"${{targets.destdir}}/opt/flutter/bin/flutter" precache --web \
--no-android --no-ios --no-linux \
--no-macos --no-windows --no-fuchsia --no-universal
rm /tmp/flutter.tar.xz