OCI Image Hardening Cheat Sheet for DevSecOps [2026]
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 $IMAGEBuild 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.tgzOCI 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 $IMAGEGenerate 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.jsonBefore 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 $IMAGESearchable Command Cheat Sheet
| Shortcut | Action |
|---|---|
/ | Focus the live filter |
g | Show build-related groups |
s | Show signing and SBOM groups |
v | Show verification and scan groups |
n or Esc | Clear the filter |
Build with attestations
docker buildx build \
--pull \
--provenance=mode=max \
--sbom=true \
--push \
-t $IMAGE .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 .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.tgzGate on vulnerabilities
trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 $IMAGE
trivy image --format json --output result.json $IMAGE
trivy clean --allExport standard SBOMs
syft scan registry:$IMAGE -o spdx-json=sbom.spdx.json
syft scan registry:$IMAGE -o cyclonedx-json=sbom.cdx.jsonSign and verify
cosign sign $IMAGE
cosign verify $IMAGE
cosign sign --key cosign.key $IMAGE
cosign verify --key cosign.pub $IMAGE
cosign verify-attestation $IMAGEConfiguration 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/trivyMinimal Trivy config file
quiet: false
timeout: 10m
cache:
dir: ./.cache/trivyPolicy 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.
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
| Topic | Primary source |
|---|---|
| OCI image and annotations | OCI Image Spec and OCI Annotations Spec |
| Docker build hardening | Dockerfile best practices |
| Buildx annotations and attestations | docker buildx build and Build attestations |
| Build secrets | Docker build secrets |
| Signing and verification | Sigstore Cosign signing and Sigstore Cosign verify |
| Scanning and config | Trivy image and Trivy configuration |
| SBOM export | Syft CLI reference and Syft output formats |
Frequently Asked Questions
What is the fastest way to harden an OCI image in CI? +
--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? +
Should I fail builds on HIGH findings or only CRITICAL? +
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? +
Get Engineering Deep-Dives in Your Inbox
Weekly breakdowns of architecture, security, and developer tooling — no fluff.