Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild
Back to Blog Best Practices

Container Image Security: Scan, Sign, and Pin Your Base Images

Sean

Platform Writer

Jul 07, 2026
7 min read

Container image security is four habits layered: scan for CVEs on every build, sign the image so consumers can verify provenance, pin base images by digest (not tag) so the build is reproducible, and run as non-root. The team that does all four ships images that pass a security audit; the team that does one of them has a partial defense. The tooling: Trivy, Grype, or Snyk for scanning; cosign for signing; digest-pinned Dockerfiles; USER directive in the Dockerfile.

Container Image Security: Scan, Sign, and Pin Your Base Images

Table of contents

Layer 1: scan for CVEs

Scan on every build. Tools:

  • Trivy - the most popular, scans OS packages and language deps, free, fast.

  • Grype - from Anchore, similar coverage, good for CI.

  • Snyk - commercial but has a free tier, deep npm/Maven/PyPI coverage.

  • Docker Scout - built into Docker Hub, automatic on push.

In CI:


trivy image --severity HIGH,CRITICAL myapp:latest

Fail the build on HIGH or CRITICAL. The team that runs this in CI on every PR has a stream of CVE-driven PRs, which is the right thing - the team that has a clean image is a lie, and the audit will catch it.

Layer 2: sign the image

Signing is cryptographic proof that the image was built by who you think. Tools:

  • cosign (Sigstore) - the modern default, keyless with OIDC, or key-based.

  • Docker Content Trust (DCT) - the older Docker-native signing, uses Notary.

Sign after the build, push the signature:


cosign sign --key cosign.key myregistry/myapp:v1.0.0

Verify on pull:


cosign verify --key cosign.pub myregistry/myapp:v1.0.0

The team that signs images can prove to a customer or auditor that the image they are running is the one the team built. The team that does not sign is hoping no one tampers with the registry.

Layer 3: pin base images by digest

Tags are mutable - ubuntu:22.04 can change when Canonical updates. Digests are immutable - ubuntu@sha256:abc123... is one specific image. Pin by digest:


FROM ubuntu@sha256:7c75ab2b0567edbb9d48fc6c4a6f5f2ad2a8c5f6e1b2a3c4d5e6f7a8b9c0d1e2

The trade-off: you have to update the digest manually when you want a new base image. The team that automates this with Dependabot or Renovate gets the digest updates as PRs.

For internal base images, pin the digest. For a one-off dev build, the tag is fine. The team that has a production image always pins the digest.

Layer 4: run as non-root

Default: most container images run as root. The fix:


RUN adduser --system --uid 1001 myapp

USER myapp

Or in Kubernetes:


securityContext:

  runAsNonRoot: true

  runAsUser: 1001

  readOnlyRootFilesystem: true

  allowPrivilegeEscalation: false

The team that runs as non-root in production has reduced the blast radius of a container escape. The team that runs as root accepts that any container escape gives the attacker root in the host.

Putting it together: a secure Dockerfile


FROM ubuntu@sha256:<digest>

RUN apt-get update && apt-get install -y --no-install-recommends \

    ca-certificates curl \

    && rm -rf /var/lib/apt/lists/*

RUN adduser --system --uid 1001 myapp

COPY --chown=myapp:myapp myapp /usr/local/bin/myapp

USER myapp

EXPOSE 8080

ENTRYPOINT ["/usr/local/bin/myapp"]

Pin by digest, minimal packages, non-root user, explicit entrypoint. The team that starts with this and adds as needed ships a clean image.

FAQ

What is the difference between scanning and signing?

Scanning checks for known vulnerabilities (CVEs) in the image’s contents. Signing proves the image was built by who you think. The team that scans but does not sign knows the image is clean but cannot prove it was built by them. The team that signs but does not scan has a provable but vulnerable image. Both are needed.

Do I really need to pin by digest?

Yes for production. A tagged image can be replaced upstream without you knowing. The team that pulls ubuntu:22.04 today and ubuntu:22.04 next month might get different content. Pinning by digest makes the build reproducible.

What is the most common container security mistake?

Running as root. The team that ships a Dockerfile without a USER directive has the default. The team that uses USER (or the equivalent in Kubernetes securityContext) has reduced the blast radius of a compromise.

Are Trivy and Snyk the same?

They overlap significantly. Trivy is free, Snyk has a free tier and a paid tier with deeper features. The team that is starting picks Trivy; the team that has a large portfolio and needs SBOM generation and policy-as-code picks Snyk.

Should I scan in CI or on registry push?

Both. CI scanning catches issues before merge. Registry scanning (Docker Scout, ECR image scanning) catches the image that is actually deployed. The team that does both has defense in depth.

How this fits the rest of the stack

For a sense of what the full project costs before it commits, the RunxBuild hosting calculator shows the line items together. The API, the database, the storage, the worker, the bandwidth - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers.

Useful related references:

#container#security#docker#cve