Safeguard
Containers

Docker Images Format Explained: Layers, OCI, and Security

Understanding the Docker images format, from layers and manifests to the OCI spec, is the foundation for scanning, signing, and hardening what you ship.

Marcus Chen
DevSecOps Engineer
6 min read

The Docker images format is a content-addressable bundle of a JSON manifest, a config object, and a set of tar layer blobs, standardized today under the Open Container Initiative (OCI) image specification, and understanding it is what makes container scanning, signing, and hardening actually make sense. If you have ever wondered why a "clean" application still shows vulnerabilities in a scanner, or why image sizes balloon, the answer is in the format. This guide breaks down the structure and why each part matters for security.

The anatomy of an image

A Docker image is not a single file. It is a set of components tied together by content digests:

  • Layers are tar archives, each representing a filesystem change set. A RUN apt-get install line produces one layer; a COPY produces another. Layers are stacked with a union filesystem at runtime.
  • Config is a JSON object describing how to run the image: the entrypoint, environment variables, exposed ports, and the ordered list of layer digests.
  • Manifest is a JSON document that references the config and the layers by their SHA-256 digests and declares media types.

Because everything is referenced by digest, images are content-addressable: the same layer shared by two images is stored once, and any change to a layer changes its digest and therefore the image identity. This is also why docker pull image:tag is not reproducible on its own; a tag can be repointed, whereas image@sha256:... is immutable.

Docker format versus OCI format

There are two closely related manifest formats, and knowing which you have matters for tooling. The legacy Docker image manifest v2 schema 2 (media type application/vnd.docker.distribution.manifest.v2+json) was Docker's own format. The OCI image manifest (application/vnd.oci.image.manifest.v1+json) is the vendor-neutral standard that emerged from it and is what most registries and tools now prefer.

They are structurally almost identical, and modern tooling reads both. You can inspect what you actually have without pulling the whole image:

docker buildx imagetools inspect nginx:1.27 --raw

Or with skopeo, which is excellent for poking at registries directly:

skopeo inspect --raw docker://docker.io/library/nginx:1.27

The mediaType field in the output tells you whether you are looking at the Docker or the OCI format. When you build with modern BuildKit, you can control the output explicitly:

docker buildx build --output type=image,oci-mediatypes=true -t myapp:1.0 .

Multi-arch images and the index

A single tag like nginx:1.27 usually points to an image index (or "manifest list"), not one image. The index maps platforms (linux/amd64, linux/arm64) to per-platform manifests, so a pull on an ARM machine fetches the ARM variant automatically. This matters for security because a scan of the amd64 variant does not automatically cover the arm64 one; they are different layer sets and can have different package versions. Scan the platform you actually deploy.

Why the format drives security

Almost every container-security practice is really a fact about the format:

Layers preserve deleted secrets. If a Dockerfile copies a file with credentials in one layer and deletes it in a later layer, the secret is still present in the earlier layer's tar blob. docker history and layer extraction recover it. The fix is to never introduce the secret into a layer in the first place, using build secrets or multi-stage builds rather than deleting after the fact.

Base image layers carry most of your CVEs. When a scanner reports vulnerabilities against an image you did not write code for, it is reading the OS packages baked into base-image layers. That is why the SCA and container scanning workflow treats base-image provenance as a first-class input, not an afterthought. Choosing a minimal base (distroless, Alpine, or slim variants) directly reduces the package count a scanner can find something in.

Digests enable trust. Because the manifest lists layers by digest, you can sign the manifest and verify it later. Pinning to image@sha256:... in production plus signature verification is what stops a tag from being silently repointed to a malicious image.

Reading an image's real contents

To understand what you are shipping, generate a software bill of materials (SBOM) from the image. Modern tools walk the layers and enumerate every OS and language package:

syft nginx:1.27 -o spdx-json > nginx-sbom.json

That SBOM is the input to vulnerability scanning and to answering "is library X anywhere in this image" during the next Log4Shell-style event. An image without an SBOM is a black box, and black boxes are where old, forgotten packages accumulate. Pairing the SBOM with a scanner such as Safeguard turns the layer structure into an actionable list rather than a pile of tar files.

Practical hardening tied to the format

  • Use multi-stage builds so build-time tooling never lands in the final layer set.
  • Order Dockerfile instructions so rarely-changing layers come first, improving cache reuse and shrinking rebuild churn.
  • Pin base images by digest, not just tag.
  • Scan every platform variant in a multi-arch index, not just the one on your laptop.

FAQ

What is the difference between the Docker image format and the OCI format?

They are structurally near-identical. The Docker manifest v2 schema 2 was Docker's original format; the OCI image manifest is the vendor-neutral standard derived from it. Modern registries and tools handle both, and the mediaType field tells you which one you have.

Why does a scanner find vulnerabilities in an image I didn't write code for?

Those findings come from OS and language packages baked into the base-image layers. The Docker images format stacks your code on top of those layers, so a scanner reads the whole stack. Using a minimal base image reduces this surface.

Can a deleted file still be in a Docker image?

Yes. Layers are immutable change sets, so a file added in one layer and deleted in a later one still exists in the earlier layer's blob. This is why secrets must never be introduced into any layer, even temporarily.

How do I see what packages are inside a Docker image?

Generate an SBOM with a tool like syft, which walks each layer and lists every OS and language package. Feed that SBOM into a vulnerability scanner to get an actionable findings list per image.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.