Home Posts OCI Image Hardening Cheat Sheet for DevSecOps [2026]
Developer Reference

OCI Image Hardening Cheat Sheet for DevSecOps [2026]

OCI Image Hardening Cheat Sheet for DevSecOps [2026]
Dillip Chowdary
Dillip Chowdary
Tech Entrepreneur & Innovator · May 03, 2026 · 14 min read

Bottom Line

If you only change three defaults, pin base images by digest, build with BuildKit attestations, and verify signatures before deploy. OCI hardening is strongest when metadata, scanning, and trust checks run in the same CI path.

Key Takeaways

  • Pin base images by tag + digest, not tag alone.
  • Use BuildKit to attach SBOM and provenance at build time.
  • Pass secrets with --secret and RUN --mount=type=secret, never ARG or ENV.
  • Gate releases with Trivy scan failures and Cosign verification, not manual review.

OCI-compliant images are no longer just tarballs with tags. As of May 03, 2026, mainstream tooling can attach SBOM, provenance, signatures, and OCI annotations directly in the registry, which means hardening now starts at build time, not after deployment. This cheat sheet focuses on the highest-leverage defaults: smaller bases, secret-safe builds, attestations, signature verification, and CI gates that stop risky images before they ship.

  • Pin base images by tag + digest, not tag alone.
  • Generate SBOM and provenance during the build, not as an afterthought.
  • Use non-root runtime users and keep build-only tools out of the final stage.
  • Verify trust data with Cosign and block risky images with Trivy.

Quickstart Baseline

Bottom Line

Harden the image where it is created: choose a smaller trusted base, pin by digest, emit SBOM and provenance, then require a passing scan and a verifiable signature before promotion.

12-point baseline

  • Use multi-stage builds to keep compilers and package managers out of the runtime image.
  • Choose a minimal base image from a trusted publisher.
  • Pin the base with a digest for reproducible rebuilds.
  • Rebuild regularly with --pull and use --no-cache when you need a fully fresh dependency resolution.
  • Exclude noise with .dockerignore.
  • Run the workload as a non-root user when the service does not need elevated privileges.
  • Never pass secrets with ARG or ENV during the build.
  • Attach OCI metadata with --annotation and labels.
  • Generate SBOM and provenance during the build.
  • Push directly to a registry when you want attestations to persist.
  • Scan the final image and fail CI on the severity threshold you actually enforce.
  • Sign and verify the image digest before deploy.

Fast-path pipeline

IMAGE=ghcr.io/acme/api:$(git rev-parse --short HEAD)

# Build, annotate, and attach attestations
 docker buildx build \
  --pull \
  --provenance=mode=max \
  --sbom=true \
  --annotation "index:org.opencontainers.image.source=https://github.com/acme/api" \
  --push \
  -t $IMAGE .

# Scan and gate
trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 $IMAGE

# Sign and verify
cosign sign $IMAGE
cosign verify $IMAGE
Watch out: With the default Docker Engine image store, attestations persist when you push to a registry. If you build and only load locally, plan for different verification and export behavior.

Build Hardening

Dockerfile defaults that reduce attack surface

  • Separate build and runtime stages so the final image contains only the app and required runtime libraries.
  • Keep the runtime image small to reduce package count and CVE surface area.
  • Pin base images by tag + digest to preserve an audit trail.
  • Set a USER for workloads that do not require privileges.
  • Prefer explicit metadata over ad hoc tags by using OCI labels and annotations.
# syntax=docker/dockerfile:1
FROM <builder-image> AS build
WORKDIR /src
COPY . .
RUN <build-command>

FROM alpine:<minor>@sha256:<digest>
RUN addgroup -S app && adduser -S app -G app
COPY --from=build /out/app /usr/local/bin/app
USER app
ENTRYPOINT ["app"]

Build secrets without leaking them into layers

Docker’s guidance is explicit: build secrets belong in secret mounts or SSH mounts, not in ARG or ENV, because those values can persist in the final image or build history.

# CLI
 docker buildx build \
  --secret id=aws,src=$HOME/.aws/credentials \
  -t app:secure .

# Dockerfile
RUN --mount=type=secret,id=aws \
    AWS_SHARED_CREDENTIALS_FILE=/run/secrets/aws \
    aws s3 cp s3://private-bucket/dependency.tgz /tmp/dependency.tgz

OCI metadata that is worth keeping

  • org.opencontainers.image.source to link the image back to source control.
  • org.opencontainers.image.revision to record the source revision that produced the image.
  • org.opencontainers.image.created to keep build timestamps consistent and machine-readable.
docker buildx build \
  --annotation "index:org.opencontainers.image.source=https://github.com/acme/api" \
  --push -t $IMAGE .

Verification and Signing

Scan what you ship

  • Run the gate against the final image, not only the source tree.
  • Filter severities so the CI outcome matches your real policy.
  • Use --ignore-unfixed only when your security team has accepted that tradeoff.
trivy image $IMAGE
trivy image --severity HIGH,CRITICAL $IMAGE
trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 $IMAGE
trivy image --format json --output result.json $IMAGE

Generate SBOMs in standard formats

Use Syft when you need explicit SPDX or CycloneDX output files in CI artifacts, tickets, or downstream compliance tooling.

syft scan registry:$IMAGE -o spdx-json=sbom.spdx.json
syft scan registry:$IMAGE -o cyclonedx-json=sbom.cdx.json
syft scan registry:$IMAGE -o spdx-json=sbom.spdx.json -o cyclonedx-json=sbom.cdx.json

Before sharing scan output or SBOM fragments outside engineering, redact secrets and accidental identifiers with the Data Masking Tool.

Sign and verify what goes to production

  • Keyless signing is the fastest secure default when your CI has OIDC available.
  • Use key-based signing when your organization requires managed keys or KMS integration.
  • Verify signatures and attestations before promotion, not only after an incident.
# Keyless signing
cosign sign $IMAGE

# Key-based signing
cosign sign --key cosign.key $IMAGE
cosign verify --key cosign.pub $IMAGE

# Verify signatures and attestations
cosign verify $IMAGE
cosign verify-attestation $IMAGE

Searchable Command Cheat Sheet

6 groups
ShortcutAction
/Focus the live filter
gShow build-related groups
sShow signing and SBOM groups
vShow verification and scan groups
n or EscClear the filter
Build

Build with attestations

docker buildx build \
  --pull \
  --provenance=mode=max \
  --sbom=true \
  --push \
  -t $IMAGE .
Freshness

Refresh dependencies

docker build --pull -t my-image:latest .
docker build --no-cache -t my-image:latest .
docker build --pull --no-cache -t my-image:latest .
Secrets

Pass build secrets safely

docker buildx build --secret id=aws,src=$HOME/.aws/credentials .
RUN --mount=type=secret,id=aws \
    AWS_SHARED_CREDENTIALS_FILE=/run/secrets/aws \
    aws s3 cp s3://private-bucket/dependency.tgz /tmp/dependency.tgz
Scan

Gate on vulnerabilities

trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 $IMAGE
trivy image --format json --output result.json $IMAGE
trivy clean --all
SBOM

Export standard SBOMs

syft scan registry:$IMAGE -o spdx-json=sbom.spdx.json
syft scan registry:$IMAGE -o cyclonedx-json=sbom.cdx.json
Trust

Sign and verify

cosign sign $IMAGE
cosign verify $IMAGE
cosign sign --key cosign.key $IMAGE
cosign verify --key cosign.pub $IMAGE
cosign verify-attestation $IMAGE

Configuration and Policy

Useful environment defaults

For Trivy, the precedence is CLI flags over environment variables over configuration file. For Buildx metadata, explicit environment defaults are a clean way to make CI output predictable.

export BUILDX_METADATA_PROVENANCE=max
export BUILDX_METADATA_WARNINGS=1

export TRIVY_SEVERITY=HIGH,CRITICAL
export TRIVY_CACHE_DIR=$PWD/.cache/trivy

Minimal Trivy config file

quiet: false
timeout: 10m
cache:
  dir: ./.cache/trivy

Policy decisions worth documenting

  • Which severity threshold blocks merges or releases.
  • Whether --ignore-unfixed is allowed in CI, and under what exception process.
  • Which OCI annotations are required on release images.
  • Whether unsigned images are blocked before deploy or only before production promotion.
  • Whether provenance must be mode=max for externally distributed artifacts.
Pro tip: If you publish both internal and public images, define different policy profiles. The right default for a public artifact is usually stricter than the right default for an ephemeral preview build.

Advanced Usage

Per-platform annotations and multi-arch builds

Buildx lets you annotate the index, manifests, or descriptors, and even target a single platform manifest. This is useful when one architecture needs extra metadata or an explicit rollout marker.

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --annotation "manifest[linux/amd64]:foo=bar" \
  --push -t $IMAGE .

Metadata files for CI evidence

When you need an artifact your pipeline can archive or inspect later, write build metadata to a file and expand provenance in the output.

export BUILDX_METADATA_PROVENANCE=max
export BUILDX_METADATA_WARNINGS=1

docker buildx build --load --metadata-file metadata.json .

When to push directly instead of load locally

  • Push directly when you need registry-visible SBOM, provenance, annotations, and signature workflows.
  • Load locally when the goal is quick local testing and you do not need registry-attached attestations yet.
  • Use a registry-backed path for release candidates so verification steps match production reality.

Official references

TopicPrimary source
OCI image and annotationsOCI Image Spec and OCI Annotations Spec
Docker build hardeningDockerfile best practices
Buildx annotations and attestationsdocker buildx build and Build attestations
Build secretsDocker build secrets
Signing and verificationSigstore Cosign signing and Sigstore Cosign verify
Scanning and configTrivy image and Trivy configuration
SBOM exportSyft CLI reference and Syft output formats

Frequently Asked Questions

What is the fastest way to harden an OCI image in CI? +
Start with four defaults: pin the base by digest, build with BuildKit using --sbom=true and --provenance=mode=max, run a Trivy gate, and require Cosign verification before deploy. That gives you reproducibility, inventory, provenance, and trust checks in one path.
Do SBOMs and provenance stay attached if I build and load locally? +
Not always. Docker documents that the default Engine image store does not persist attestations the same way a direct registry push does, so release pipelines should usually push the image when they need registry-visible SBOM and provenance data.
Should I fail builds on HIGH findings or only CRITICAL? +
That is a policy decision, not a tooling limitation. Use trivy image --severity HIGH,CRITICAL --exit-code 1 when your organization wants a strict gate, and document when --ignore-unfixed is acceptable so engineers are not guessing during an incident.
What is the difference between OCI annotations and attestations? +
Annotations are metadata fields attached to the image index, manifest, or descriptor. Attestations are separate signed statements about the artifact, such as SBOM or provenance, and tools like BuildKit and Cosign use them for supply-chain evidence and verification.

Get Engineering Deep-Dives in Your Inbox

Weekly breakdowns of architecture, security, and developer tooling — no fluff.

Found this useful? Share it.