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 .
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"]}
]
}
}
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:tagfor manifest,skopeo copy docker://image:tag oci:./local:tagto dump everything. - What's running inside? From the host:
nsenter -t <pid> -a /bin/shenters all namespaces of the target. - Why is it slow?
nsenter -t <pid> -a perf topprofiles inside the container. - What syscalls is it making?
strace -f -p <host-pid>works through namespaces.