Skip to content

remotecache/s3: honor compression attributes - #7121

Open
ernetas wants to merge 2 commits into
moby:masterfrom
ernetas:s3-cache-compression
Open

remotecache/s3: honor compression attributes#7121
ernetas wants to merge 2 commits into
moby:masterfrom
ernetas:s3-cache-compression

Conversation

@ernetas

@ernetas ernetas commented Sep 5, 2026

Copy link
Copy Markdown

--export-cache type=s3 silently ignores compression, compression-level and force-compression: the exporter returns a hardcoded compression.New(compression.Default) from Config(), so every layer is exported as gzip.

buildctl build ... \
  --export-cache type=s3,region=eu-west-1,bucket=my_bucket,compression=zstd,force-compression=true
# layers are still application/vnd.oci.image.layer.v1.tar+gzip

That value was never deliberate. e3f6d7b added Config() to the Exporter interface and wired compression.ParseAttributes into registry and local, leaving a placeholder returning the default in gha and inline; the s3 backend landed two months later in 09c5a7c and, having to satisfy the now-mandatory method, copied that placeholder rather than the registry implementation. Nothing in the s3 format is tied to gzip — Finalize already records the real media type of every layer and the importer reads it back, so blobs of any supported compression round-trip.

Changes

remotecache/s3: honor compression attributes — parse the attributes in ResolveCacheExporterFunc and return them from Config(), the same way registry and local do. Tests: a table test over the parsing (each valid value, and the error each invalid one produces), plus two integration tests that export, re-import, and assert the cache manifest records the requested media type and that the uploaded blobs really are in that format:

  • testZstdS3CacheImportExport also exports llb.Image("busybox").Run(...) under a second manifest name. That build is what pins force-compression — the first only creates layers born in the requested compression, while this one has to convert the gzip base image layers.
  • testUncompressedS3CacheImportExport covers the other end of the range, where the blob digest equals the diffID and the media type carries no compression suffix.

test: isolate mc config dir for minio servers — a prerequisite. mc keeps its aliases in a single folder defaulting to $HOME/.mc and NewMinioServer registers an alias there per server, so concurrent servers race while rewriting it: mc mb intermittently created the bucket on another test's server and the export then failed with NoSuchBucket. Each server now gets its own folder through MC_CONFIG_DIR, set in the environment rather than passed as --config-dir so that no invocation can miss it — mc treats <alias>/<bucket> of an unknown alias as a local path and silently succeeds.

README documents the three attributes, with a caveat that estargz layers are recorded as plain gzip in the s3 manifest and so cannot be lazily pulled from s3: LayerAnnotations has nowhere to keep their eStargz annotations.

Testing

$ go test ./cache/remotecache/s3/            # and with AWS_PROFILE=nonexistent
ok  	github.com/moby/buildkit/cache/remotecache/s3

$ TESTPKGS=./client TESTFLAGS="--run TestIntegration/Test.*S3CacheImportExport -v" ./hack/test integration
DONE 28 tests in 14.439s   # 9 workers, all pass, 0 skips

Both new integration tests fail without the Config() change, e.g.:

--- FAIL: TestIntegration/TestUncompressedS3CacheImportExport/worker=oci
    layer sha256:3a4bc23… has media type "application/vnd.oci.image.layer.v1.tar+gzip", expected uncompressed

Notes

  • Behavior change: an invalid compression, compression-level or force-compression value on type=s3 now fails when the exporter is resolved — before any build work, and regardless of ignore-error — instead of being silently ignored. This matches registry and local.
  • azblob and gha still carry the same placeholder; the gha one is noted in Support ZSTD compression in s3 remote cache #3071. Left out here to keep the change focused.

Fixes #3071

mc stores its aliases in a single configuration folder that defaults to
$HOME/.mc. NewMinioServer registers an alias there for every server it
starts, so two servers running at the same time race each other while
rewriting that file: one invocation can drop the alias another one just
registered, and a subsequent mc call then either fails outright or
resolves the alias to the wrong server. In the latter case the bucket
gets created on a different server than the one the test handed to
buildkitd, which later fails the export with NoSuchBucket.

Only a single test used a minio server so far, so the aliases never
overlapped in practice. Point the mc invocations of every server at
their own folder through MC_CONFIG_DIR so that additional parallel tests
keep working. Setting it in the environment rather than passing
--config-dir makes it impossible for an invocation to miss it, which
matters because mc treats "<alias>/<bucket>" of an unknown alias as a
local path and silently succeeds. As the folder is a t.TempDir(), the
alias no longer needs a random name or an explicit cleanup.

Signed-off-by: Ernestas Lukoševičius <ernetas@gmail.com>
@ernetas
ernetas force-pushed the s3-cache-compression branch from 95a899d to 73389f4 Compare September 5, 2026 13:04
The s3 cache exporter returned a hardcoded compression.New(compression.
Default) from Config(), so compression, compression-level and
force-compression passed to --export-cache type=s3 were silently ignored
and every layer was always exported as gzip.

That value was never a deliberate choice for this backend. e3f6d7b
added Config() to the Exporter interface to make the compression
selectable, and wired compression.ParseAttributes into the registry and
local backends while leaving a placeholder returning the default in gha
and inline. The s3 backend was added two months later in 09c5a7c and
copied that placeholder instead of the registry implementation.

Nothing in the s3 cache format is tied to gzip. Finalize already records
the real media type of every layer in the cache manifest and the importer
reads it back, so blobs of any supported compression round-trip.

Parse the attributes in ResolveCacheExporterFunc and return them from
Config(), the same way the registry and local backends do.

Fixes moby#3071

Signed-off-by: Ernestas Lukoševičius <ernetas@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support ZSTD compression in s3 remote cache

1 participant