build: Add per-step CPU and memory resource limits

Port of moby/buildkit#6569 to buildx. Adds --memory, --memory-swap,
--cpu-shares, --cpu-period, --cpu-quota, --cpuset-cpus, and --cpuset-mems
flags to build, plus the equivalent bake target attributes and compose
x-bake fields.

Signed-off-by: Jiří Moravčík <jiri.moravcik@gmail.com>
This commit is contained in:
Jiří Moravčík
2026-06-08 15:39:14 +02:00
parent 4fbc1e5f2c
commit 80b2293881
14 changed files with 761 additions and 48 deletions
+51 -22
View File
@@ -89,6 +89,11 @@ type buildOptions struct {
target string
ulimits *dockeropts.UlimitOpt
resources []string
// legacyResources is kept separate from resources because the pflag
// StringArray for --resource resets its backing slice on first use.
legacyResources []string
attests []string
sbom string
provenance string
@@ -133,6 +138,7 @@ func (o *buildOptions) toOptions() (*BuildOptions, error) {
Tags: o.tags,
Target: o.target,
Ulimits: o.ulimits,
Resources: append(o.legacyResources, o.resources...),
Builder: o.builder,
NoCache: o.noCache,
Pull: o.pull,
@@ -234,6 +240,20 @@ func (o *buildOptions) toDisplayMode() (progressui.DisplayMode, error) {
return progress, nil
}
// legacyResourceValue is a pflag.Value that appends a "<key>=value" entry to
// the --resource list, wiring the hidden legacy flags into the same code path.
type legacyResourceValue struct {
key string
resources *[]string
}
func (v *legacyResourceValue) String() string { return "" }
func (v *legacyResourceValue) Type() string { return "string" }
func (v *legacyResourceValue) Set(s string) error {
*v.resources = append(*v.resources, v.key+"="+s)
return nil
}
const (
commandNameAttribute = attribute.Key("command.name")
commandOptionsHash = attribute.Key("command.options.hash")
@@ -583,6 +603,8 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugger debuggerOpt
options.ulimits = dockeropts.NewUlimitOpt(nil)
flags.Var(options.ulimits, "ulimit", "Ulimit options")
flags.StringArrayVar(&options.resources, "resource", []string{}, `Resource limits for build containers (format: "memory=2g", "cpu-quota=50000")`)
flags.StringArrayVar(&options.attests, "attest", []string{}, `Attestation parameters (format: "type=sbom,generator=image")`)
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`)
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--attest=type=provenance"`)
@@ -595,12 +617,33 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugger debuggerOpt
var ignore string
var ignoreSlice []string
var ignoreBool bool
var ignoreInt int64
flags.StringVar(&options.callFunc, "print", "", "Print result of information request (e.g., outline, targets)")
cobrautil.MarkFlagsExperimental(flags, "print")
flags.MarkHidden("print")
// Legacy per-resource flags, hidden and superseded by --resource.
flags.VarP(&legacyResourceValue{key: "memory", resources: &options.legacyResources}, "memory", "m", "Memory limit")
flags.MarkHidden("memory")
flags.VarP(&legacyResourceValue{key: "memory-swap", resources: &options.legacyResources}, "memory-swap", "", `Swap limit equal to memory plus swap: "-1" to enable unlimited swap`)
flags.MarkHidden("memory-swap")
flags.VarP(&legacyResourceValue{key: "cpu-shares", resources: &options.legacyResources}, "cpu-shares", "c", "CPU shares (relative weight)")
flags.MarkHidden("cpu-shares")
flags.VarP(&legacyResourceValue{key: "cpu-period", resources: &options.legacyResources}, "cpu-period", "", "Limit the CPU CFS (Completely Fair Scheduler) period")
flags.MarkHidden("cpu-period")
flags.VarP(&legacyResourceValue{key: "cpu-quota", resources: &options.legacyResources}, "cpu-quota", "", "Limit the CPU CFS (Completely Fair Scheduler) quota")
flags.MarkHidden("cpu-quota")
flags.VarP(&legacyResourceValue{key: "cpuset-cpus", resources: &options.legacyResources}, "cpuset-cpus", "", `CPUs in which to allow execution ("0-3", "0,1")`)
flags.MarkHidden("cpuset-cpus")
flags.VarP(&legacyResourceValue{key: "cpuset-mems", resources: &options.legacyResources}, "cpuset-mems", "", `MEMs in which to allow execution ("0-3", "0,1")`)
flags.MarkHidden("cpuset-mems")
flags.BoolVar(&ignoreBool, "compress", false, "Compress the build context using gzip")
flags.MarkHidden("compress")
@@ -617,27 +660,6 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugger debuggerOpt
flags.SetAnnotation("squash", "flag-warn", []string{"experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency."})
cobrautil.MarkFlagsExperimental(flags, "squash")
flags.StringVarP(&ignore, "memory", "m", "", "Memory limit")
flags.MarkHidden("memory")
flags.StringVar(&ignore, "memory-swap", "", `Swap limit equal to memory plus swap: "-1" to enable unlimited swap`)
flags.MarkHidden("memory-swap")
flags.Int64VarP(&ignoreInt, "cpu-shares", "c", 0, "CPU shares (relative weight)")
flags.MarkHidden("cpu-shares")
flags.Int64Var(&ignoreInt, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
flags.MarkHidden("cpu-period")
flags.Int64Var(&ignoreInt, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
flags.MarkHidden("cpu-quota")
flags.StringVar(&ignore, "cpuset-cpus", "", `CPUs in which to allow execution ("0-3", "0,1")`)
flags.MarkHidden("cpuset-cpus")
flags.StringVar(&ignore, "cpuset-mems", "", `MEMs in which to allow execution ("0-3", "0,1")`)
flags.MarkHidden("cpuset-mems")
flags.BoolVar(&ignoreBool, "rm", true, "Remove intermediate containers after a successful build")
flags.MarkHidden("rm")
@@ -990,6 +1012,7 @@ type BuildOptions struct {
Tags []string
Target string
Ulimits *dockeropts.UlimitOpt
Resources []string
Builder string
NoCache bool
Pull bool
@@ -1014,6 +1037,11 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *BuildOptions, inSt
contexts[name] = build.NamedContext{Path: path}
}
resourceLimits, err := build.ParseResourceLimits(in.Resources)
if err != nil {
return nil, nil, err
}
opts := build.Options{
Inputs: build.Inputs{
ContextPath: in.ContextPath,
@@ -1034,6 +1062,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *BuildOptions, inSt
Tags: in.Tags,
Target: in.Target,
Ulimits: in.Ulimits,
ResourceLimits: resourceLimits,
GroupRef: in.GroupRef,
ProvenanceResponseMode: confutil.ParseMetadataProvenance(in.ProvenanceResponseMode),
}
+35
View File
@@ -110,6 +110,13 @@ type configOutput struct {
ShmSize string `json:",omitempty"`
Ulimit string `json:",omitempty"`
Memory string `json:",omitempty"`
MemorySwap string `json:",omitempty"`
CPUShares string `json:",omitempty"`
CPUPeriod string `json:",omitempty"`
CPUQuota string `json:",omitempty"`
CPUSetCPUs string `json:",omitempty"`
CPUSetMems string `json:",omitempty"`
CacheMountNS string `json:",omitempty"`
DockerfileCheckConfig string `json:",omitempty"`
SourceDateEpoch string `json:",omitempty"`
@@ -391,6 +398,13 @@ workers0:
readAttr(attrs, "shm-size", &out.Config.ShmSize, nil)
readAttr(attrs, "ulimit", &out.Config.Ulimit, nil)
readAttr(attrs, "memory", &out.Config.Memory, nil)
readAttr(attrs, "memswap", &out.Config.MemorySwap, nil)
readAttr(attrs, "cpushares", &out.Config.CPUShares, nil)
readAttr(attrs, "cpuperiod", &out.Config.CPUPeriod, nil)
readAttr(attrs, "cpuquota", &out.Config.CPUQuota, nil)
readAttr(attrs, "cpusetcpus", &out.Config.CPUSetCPUs, nil)
readAttr(attrs, "cpusetmems", &out.Config.CPUSetMems, nil)
readAttr(attrs, "build-arg:BUILDKIT_CACHE_MOUNT_NS", &out.Config.CacheMountNS, nil)
readAttr(attrs, "build-arg:BUILDKIT_DOCKERFILE_CHECK", &out.Config.DockerfileCheckConfig, nil)
readAttr(attrs, "build-arg:"+epoch.SourceDateEpochEnv, &out.Config.SourceDateEpoch, nil)
@@ -570,6 +584,27 @@ workers0:
if out.Config.Ulimit != "" {
fmt.Fprintf(tw, "Resource Limits:\t%s\n", out.Config.Ulimit)
}
if out.Config.Memory != "" {
fmt.Fprintf(tw, "Memory:\t%s\n", out.Config.Memory)
}
if out.Config.MemorySwap != "" {
fmt.Fprintf(tw, "Memory Swap:\t%s\n", out.Config.MemorySwap)
}
if out.Config.CPUShares != "" {
fmt.Fprintf(tw, "CPU Shares:\t%s\n", out.Config.CPUShares)
}
if out.Config.CPUPeriod != "" {
fmt.Fprintf(tw, "CPU Period:\t%s\n", out.Config.CPUPeriod)
}
if out.Config.CPUQuota != "" {
fmt.Fprintf(tw, "CPU Quota:\t%s\n", out.Config.CPUQuota)
}
if out.Config.CPUSetCPUs != "" {
fmt.Fprintf(tw, "CPUSet CPUs:\t%s\n", out.Config.CPUSetCPUs)
}
if out.Config.CPUSetMems != "" {
fmt.Fprintf(tw, "CPUSet Mems:\t%s\n", out.Config.CPUSetMems)
}
if out.Config.CacheMountNS != "" {
fmt.Fprintf(tw, "Cache Mount Namespace:\t%s\n", out.Config.CacheMountNS)
}