Image Digest

An image digest is the SHA-256 hash of an image manifest, so it names one exact image. How a digest differs from a tag, and how to pin one in a workflow.

A Docker image digest is the SHA-256 hash of an image manifest, written as sha256: followed by 64 hexadecimal characters, and it identifies one exact image. A tag such as app:1.4.0 is a separate thing: a name the registry stores beside a manifest, and a later push can move that name onto different content, while a digest always resolves to the bytes it was computed from.

That difference decides what a deploy actually ships. A pull by tag asks the registry what the name points at right now. A pull by digest asks for specific content, and the registry either serves that content or fails.

Definition

A digest is a content address. The OCI image specification defines it as algorithm:encoded, with SHA-256 as the algorithm every registry and client is expected to support (OCI image spec descriptor, checked on 2026-08-13).

The hashed document is the manifest, a small JSON file that lists the image config blob and every layer blob by its own digest and size. Because the manifest names its parts by hash, a change anywhere in the image propagates upward: editing a file changes a layer, the changed layer gets a new layer digest, that new digest appears in the manifest, the manifest bytes change, and the manifest digest changes with them. One 64-character string therefore covers the whole image transitively.

The identifiers a build produces

An image build produces several hashes, and they are easy to confuse because they are all rendered in the same sha256: form.

IdentifierHashed documentWhere it shows upUsable in a pull reference
Manifest digestThe manifest JSON for one platformdocker push output, RepoDigests in docker inspect, the registry Docker-Content-Digest response headerYes
Index digestThe image index JSON listing one manifest per platformThe tag of a multi-platform build, docker buildx imagetools inspectYes
Config digestThe image config JSON blobThe IMAGE ID column of docker imagesNo
Layer digestOne compressed layer blobdocker buildx imagetools inspect --raw, registry blob pathsNo
TagNothing is hashed; the registry stores a name that maps to a digestThe TAG column of docker imagesYes, and the result can change between pulls

The config digest is the row that catches people out. docker images prints it as the image ID, it is computed on the machine that built the image, and pasting it after an @ in a pull reference fails, because the registry indexes manifests rather than config blobs.

Why a tag can move and a digest cannot

The registry API stores a manifest under a name and accepts a PUT for either a tag or a digest reference. Pushing to a tag that already exists rewrites the mapping, and the previous manifest stays addressable by its own digest until it is garbage collected (OCI distribution spec, checked on 2026-08-13).

Nothing in the specification makes a version-shaped tag permanent. v1.4.0 is a convention, and a retag or a force push can point it at a different build tomorrow. Several registries offer tag immutability as an opt-in setting on a repository, which is a registry feature rather than a property of tags in general. latest carries no special meaning either; it is the tag a client assumes when a reference omits one.

A digest has the opposite property by construction. Recomputing the hash of the manifest you received is how a client verifies that it got what it asked for, so serving different bytes under the same digest fails verification at the pulling end.

Where a digest can be used

Any place that accepts an image reference accepts the name@sha256:... form:

  • docker pull ghcr.io/acme/api@sha256:... pins a pull (Docker CLI reference, checked on 2026-08-13).
  • FROM node:22@sha256:... pins a base image in a Dockerfile. The tag stays readable for humans, and the digest is what resolves.
  • image: ghcr.io/acme/api@sha256:... pins a Kubernetes pod spec (Kubernetes images documentation, checked on 2026-08-13).

Copying an image between registries preserves the digest as long as the manifest bytes travel unchanged, which is what docker buildx imagetools create and registry mirroring tools do. Rebuilding the image and pushing it somewhere else produces a different digest, even from the same commit, because timestamps and layer compression differ.

Example

A release workflow builds and pushes an image, then a second job deploys it. docker/build-push-action exposes the digest the registry returned as a step output, so the deploy job can reference the exact manifest the build job created (docker/build-push-action outputs, checked on 2026-08-13).

name: release
on:
  push:
    tags: ["v*"]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write
      attestations: write
    outputs:
      digest: ${{ steps.push.outputs.digest }}
    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: push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
      - uses: actions/attest-build-provenance@v2
        with:
          subject-name: ghcr.io/${{ github.repository }}
          subject-digest: ${{ steps.push.outputs.digest }}
          push-to-registry: true

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: |
          kubectl set image deployment/api \
            api=ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}

The build job publishes the tag for humans and passes the digest to the deploy job. The attestation step takes the same digest, so the signed provenance record is bound to that manifest rather than to the moving name.

Now suppose something touches the registry between the two jobs: a rerun of an older release workflow, a manual docker push from a laptop, or a promotion script that retags a staging build. The table traces what each deploy reference resolves to.

Event between build and deployDeploy that references :v1.4.0Deploy that references @sha256:...
Nothing happensThe image this run builtThe image this run built
Another push moves v1.4.0 onto a different buildThe other build, with no warning in the deploy logThe image this run built
The tag is deletedThe pull fails with a manifest-unknown errorThe image this run built, while the manifest is retained
The registry garbage collects the untagged manifestUnaffectedThe pull fails, which surfaces the retention policy as an error rather than as a silent substitution

The second row is the case worth designing against, because both deploys succeed and only one of them ships what was tested.

The same reasoning applies upward, to base images. Reading the digest behind a tag takes one command:

docker buildx imagetools inspect node:22 --format '{{.Manifest.Digest}}'

Pasting that value into FROM node:22@sha256:<digest from the command above> freezes the base image for every future build of the Dockerfile, and a dependency update tool can bump both the tag and the digest together when a new base is available.

FAQ

What is the difference between an image digest and a tag?

A digest is computed from the bytes of the image manifest, so the same digest always resolves to the same content. A tag is a name the registry stores beside a manifest, and a later push can point that name at a different manifest. Pulling by tag asks the registry what the name means right now; pulling by digest asks for specific content.

Is the IMAGE ID from docker images the same as the digest?

No. The IMAGE ID column shows the digest of the image config blob, which is computed locally when the image is built. The registry digest is computed over the manifest document that lists the config and the layers. The two values are different hashes of different documents, and only the manifest digest works in a pull reference.

Does a digest still work for a multi-platform image?

Yes. A multi-platform build publishes an image index that lists one manifest per platform, and the index itself has a digest. A client pulling that index digest reads the platform entries and fetches the manifest matching its own architecture, so one digest covers every platform in the build.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.