Skip to content

Appendix B-Reference Patterns

Reference recipes for the patterns you'll reach for repeatedly.


B.1 Multi-Stage Distroless (Go)

FROM golang:1.22-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/root/.cache/go-build \
    --mount=type=cache,target=/go/pkg/mod \
    go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

B.2 Multi-Stage Distroless (Rust)

FROM rust:1.78 AS build
WORKDIR /src
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/src/target \
    cargo build --release && cp target/release/app /out/app

FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

For static Rust (musl), use gcr.io/distroless/static.


B.3 Multi-Stage Distroless (Python)

FROM python:3.12-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip install --target=/install -r requirements.txt
COPY . .

FROM gcr.io/distroless/python3-debian12:nonroot
COPY --from=build /install /pkg
COPY --from=build /app /app
ENV PYTHONPATH=/pkg
USER nonroot:nonroot
ENTRYPOINT ["python", "/app/main.py"]

B.4 Multi-Arch Build with buildah

#!/usr/bin/env bash
set -euo pipefail
IMAGE=registry.local/myapp
TAG=v1.0.0

for arch in amd64 arm64; do
  buildah build --arch=$arch --manifest $IMAGE:$TAG -t $IMAGE:$TAG-$arch .
done

buildah manifest push --all $IMAGE:$TAG docker://$IMAGE:$TAG

B.5 Reproducible Build

SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
buildah build \
  --timestamp $SOURCE_DATE_EPOCH \
  --pull-never \
  --layers=false \
  -t myapp:$TAG .
Combine with deterministic dependency ordering, pinned base by digest, and no RUN apt-get update (use a frozen package mirror).


B.6 Rootless Podman + Systemd

podman run --name myapp --rm -d ...
podman generate systemd --new --name myapp > ~/.config/systemd/user/myapp.service
systemctl --user daemon-reload
systemctl --user enable --now myapp.service
loginctl enable-linger

B.7 OCI Hooks

{
  "hooks": {
    "prestart": [
      {"path": "/usr/local/bin/network-setup", "args": ["network-setup"], "timeout": 5}
    ],
    "poststop": [
      {"path": "/usr/local/bin/cleanup", "args": ["cleanup"]}
    ]
  }
}
Hooks are how custom CNI, GPU device injection, and metric collectors wire in.


B.8 CI/CD Skeleton (GitHub Actions)

name: build
on:
  push:
    branches: [main]
    tags: ['v*']

permissions:
  id-token: write          # required for cosign keyless
  contents: read
  packages: write

jobs:
  build-scan-sign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: build
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          provenance: mode=max
          sbom: true
      - name: Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
          severity: CRITICAL,HIGH
          exit-code: 1
      - uses: sigstore/cosign-installer@v3
      - name: Sign
        run: cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
      - name: Attest SBOM
        run: cosign attest --yes --type spdx --predicate sbom.json ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

B.9 Debugging Recipes

  • No shell in the image? kubectl debug -it <pod> --image=busybox --target=<container> adds an ephemeral debug container in the same namespace.
  • Inspect a layer? skopeo inspect docker://image:tag for manifest, skopeo copy docker://image:tag oci:./local:tag to dump everything.
  • What's running inside? From the host: nsenter -t <pid> -a /bin/sh enters all namespaces of the target.
  • Why is it slow? nsenter -t <pid> -a perf top profiles inside the container.
  • What syscalls is it making? strace -f -p <host-pid> works through namespaces.

Comments