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.
| Identifier | Hashed document | Where it shows up | Usable in a pull reference |
|---|---|---|---|
| Manifest digest | The manifest JSON for one platform | docker push output, RepoDigests in docker inspect, the registry Docker-Content-Digest response header | Yes |
| Index digest | The image index JSON listing one manifest per platform | The tag of a multi-platform build, docker buildx imagetools inspect | Yes |
| Config digest | The image config JSON blob | The IMAGE ID column of docker images | No |
| Layer digest | One compressed layer blob | docker buildx imagetools inspect --raw, registry blob paths | No |
| Tag | Nothing is hashed; the registry stores a name that maps to a digest | The TAG column of docker images | Yes, 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 deploy | Deploy that references :v1.4.0 | Deploy that references @sha256:... |
|---|---|---|
| Nothing happens | The image this run built | The image this run built |
Another push moves v1.4.0 onto a different build | The other build, with no warning in the deploy log | The image this run built |
| The tag is deleted | The pull fails with a manifest-unknown error | The image this run built, while the manifest is retained |
| The registry garbage collects the untagged manifest | Unaffected | The 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.
Related Terms
- How a container registry stores manifests, blobs, and tags: the storage model that makes a digest addressable and a tag rewritable.
- Provenance attestations and the digest they are bound to: what the signed record covers, and why the subject is a digest.
- Building an image once and deploying the same digest to every environment: the workflow pattern that carries a digest from build through staging to production.
- OCI image spec descriptor: the upstream definition of the digest format and the algorithms registries accept.
- WarpBuild Docker builders documentation: builder profiles and the actions that point a workflow build at a builder.
- WarpBuild security documentation: how runners are isolated and how build storage is handled.
- WarpBuild pricing: per minute rates by runner type.
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.