Skip to content

remotecache/s3: honor compression attributes - #7121

Merged
crazy-max merged 3 commits into
moby:masterfrom
ernetas:s3-cache-compression
Sep 23, 2026
Merged

crazy-max merged 3 commits into
moby:masterfrom
ernetas:s3-cache-compression

Conversation

@ernetas

@ernetas ernetas commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

--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

@ernetas

ernetas commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

@tonistiigi sorry, didn't see that this was already approved before pushing. Do you want me to drop minio test changes?

@crazy-max crazy-max added this to the v0.34.0 milestone Sep 23, 2026

@crazy-max crazy-max left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check CI, seems there are some lint issues. Also can you rebase with master

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>
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>
NewMinioServer runs mc alias set and mc mb through integration.RunCmd,
which is a plain cmd.Run on t.Context(). That context is only canceled
once the test function returns, so unlike everything else the test does
these two calls are not covered by the sandbox timeout: a wedged server
keeps the test running until the whole test binary hits its go test
deadline, and the test that hung is never reported. They are the only
RunCmd callers in the tree; every other wait in the harness is bounded.

Give each one-shot invocation a 30s deadline of its own and report which
one expired. mc admin trace is a long-running stream and keeps using
t.Context(). A failure of mc itself still surfaces as its own error
rather than as a timeout.

Signed-off-by: Ernestas Lukoševičius <ernetas@gmail.com>
@ernetas
ernetas force-pushed the s3-cache-compression branch from a77ccc3 to bfdbe22 Compare September 23, 2026 13:09
@crazy-max
crazy-max merged commit e1d0ff6 into moby:master Sep 23, 2026
249 of 250 checks passed
@ernetas
ernetas deleted the s3-cache-compression branch September 23, 2026 13:59
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

3 participants