Docker containerd image store not enabled in CI
Docker containerd image store not enabled is the real condition behind a buildx refusal that never mentions containerd, because buildx decides by reading one field in docker info and reports only the consequence. The same refusal fires for a single-platform build that carries attestations, which is why it can appear the day you turn provenance on.

What this error means
A docker buildx build --load fails immediately, before any build step runs, with a sentence about the docker exporter and manifest lists. Nothing in it names containerd, the image store, or a setting you could change. The same command with --push instead of --load succeeds, which sends people looking at the registry. A second wording exists for builds that write a tar file. The block below sets both out as the two programs that emit them format them, because no run is recorded for this page.
--- buildx refused this before the build started
ERROR: docker exporter does not currently support exporting manifest lists
--- same build with --output type=docker,dest=image.tar
ERROR: docker exporter does not support exporting manifest lists, use the oci exporter insteadThe check buildx makes, and what it does with the answer
Before a build starts, buildx asks the daemon for its info and looks through the driver status entries for a pair whose first element is driver-type and whose second is io.containerd.snapshotter.v1. That single test is what it calls the OCI importer feature.
If the feature is present and you did not ask for a file on disk, buildx silently rewrites your docker export into an oci export, because the OCI importer handles multi-platform images. If it is absent, and you asked for more than one platform or for any attestation, it refuses, and the wording depends on whether you asked for a file. Neither wording mentions the test it just failed.
Note the second half of that condition. More than one platform is the famous trigger, but attestations trigger it too, on a perfectly ordinary single-platform build. A workflow that adds provenance for supply chain reasons can start failing with a message about manifest lists it never asked for.
| What the job asked for | Classic image store | containerd image store |
|---|---|---|
One platform, no attestations, --load | Loads normally | Loads normally |
Two or more platforms, --load | Refused before the build | Loads, through the OCI importer |
One platform with provenance or SBOM, --load | Refused before the build | Loads, through the OCI importer |
Any of the above with --output type=docker,dest= | Refused, with the wording naming the oci exporter | Refused, because a file output is not importable |
Common causes
The daemon is using the classic image store
The classic store holds one platform per image, so it has no way to keep a manifest list locally. Buildx knows this from the driver status and refuses rather than building something it cannot import. Most Linux CI runners ship this way unless the daemon configuration says otherwise.
Attestations were turned on and nobody changed the platform list
Provenance and SBOM attestations are described in a format the docker exporter cannot carry, so buildx treats a build with attestations the same way it treats a multi-platform build. In our experience this is the confusing one, because the message talks about manifest lists and the workflow only ever built one platform.
The build writes a tar file rather than loading
When an output destination is set, buildx cannot substitute the OCI importer, because there is nothing to import into. That path refuses on both image stores and says so in different words, naming the oci exporter as the alternative. Enabling the containerd store does not help here.
The setting was applied but the daemon was not restarted
The image store is chosen when the daemon starts. Writing the feature flag into the daemon configuration file changes nothing until the daemon reads it again, and a job that edits the file and immediately builds will see the old store. The check in the step above will say so plainly.
How to fix it
Turn on the containerd snapshotter for the job
- Write the feature flag into the daemon configuration file on the runner.
- Restart the daemon and wait for it to come back before the next step.
- Assert the driver status shows the containerd snapshotter, so a silent failure to restart fails loudly instead.
- name: Enable the containerd image store
run: |
echo '{ "features": { "containerd-snapshotter": true } }' \
| sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
timeout 60 bash -c 'until docker info >/dev/null 2>&1; do sleep 2; done'
docker info --format "{{json .DriverStatus}}" | grep -q snapshotter \
|| { echo "::error::containerd image store did not come up"; exit 1; }Or stop asking for a local multi-platform image
Most pipelines load an image only to run tests against it, and tests run on one architecture at a time. Load the platform the job will actually use and push the full set separately. This is the cheaper fix and it survives a runner image change.
docker buildx build --platform linux/amd64 --load -t acme/api:test .
docker buildx build --platform linux/amd64,linux/arm64 --push -t acme/api:1.4.2 .Turn attestations off for the load and keep them for the push
If the build is single-platform and the failure appeared when provenance did, split the concerns. The load exists to run tests and does not need attestations; the push does. Turning them off on the load is not giving anything up, because nothing consumes an attestation from the local image store.
docker buildx build --load --provenance=false --sbom=false -t acme/api:test .For a tar output, ask for the OCI layout the message names
The second wording tells you the fix outright. The oci exporter writes a layout that holds a manifest list, and tools that consume image archives increasingly read it. Change the output type rather than enabling the image store, because the store is not what blocked this one.
docker buildx build --platform linux/amd64,linux/arm64 \
--output type=oci,dest=image.tar -t acme/api:1.4.2 .Confirm the store before you change anything
The check buildx makes is one you can make yourself in a step, and it answers the question directly rather than by inference. Reading the storage driver name alone is not enough, because the name changes between versions and the field buildx actually reads is the driver status list.
Run this on the runner, not locally. A developer machine with Docker Desktop very often has the containerd store on while the CI runner does not, which is exactly why the build works in one place and not the other.
- name: Report the image store the way buildx reads it
run: |
docker info --format "{{json .DriverStatus}}"
docker info --format "{{.Driver}}"Why no recorded run backs this page
A recorded run of this failure would be a recording of our runner configuration. The error is produced by a client-side branch on a value the daemon reports about itself, so a log proves only which setting that particular daemon had, and a reader whose runner is configured differently learns nothing from it. The interesting content is the branch, and the branch is readable in the source without spending a runner minute.
There is also nothing to repair here. The build never started, no registry was contacted, and the condition is a daemon configuration decision made before the job existed. A recorded run would show a failure that a retry, a bigger runner and a warm cache all leave exactly where it is.
How to prevent it
- Assert the driver status in the job rather than assuming the runner image kept it.
- Keep the platform list on the load step down to the one the tests run on.
- Decide attestations per output, not once for the whole workflow.
- Wait for the daemon to answer after a restart, so a slow start does not read as the old store.
Frequently asked questions
Why does --push work when --load fails?
How does buildx decide the store is containerd?
driver-type and whose value is io.containerd.snapshotter.v1. That is the whole test. You can run the same query in a step with a JSON format string, which is more reliable than reading the storage driver name, since that name has changed between versions.Does enabling the containerd image store lose my existing images?
Why did this start failing when I added provenance?
Related guides
References
- buildx: the export setup that swaps or refuses the docker exporter
- buildx: the driver-type test behind the OCI importer feature
- BuildKit: the exporter that emits the same sentence server side
- Docker docs: the containerd image store
- Docker docs: multi-platform builds
- Docker documentation
- Docker build cache
- GitHub Actions documentation