@@ -24,6 +24,7 @@ import (
2424 "net/http"
2525 "os"
2626 "path/filepath"
27+ "time"
2728
2829 "github.com/opencontainers/go-digest"
2930 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -34,7 +35,6 @@ import (
3435 "github.com/containerd/containerd/v2/core/images/converter"
3536 "github.com/containerd/containerd/v2/core/remotes"
3637 "github.com/containerd/containerd/v2/core/remotes/docker"
37- dockerconfig "github.com/containerd/containerd/v2/core/remotes/docker/config"
3838 "github.com/containerd/containerd/v2/pkg/reference"
3939 "github.com/containerd/log"
4040 "github.com/containerd/stargz-snapshotter/estargz"
@@ -165,17 +165,29 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
165165 }
166166 dOpts = append (dOpts , dockerconfigresolver .WithHostsDirs (options .GOptions .HostsDir ))
167167
168- ho , err := dockerconfigresolver . NewHostOptions ( ctx , refDomain , dOpts ... )
169- if err != nil {
170- return err
168+ // Configure connection limits to prevent registry overload (503 errors )
169+ if options . MaxConnsPerHost > 0 {
170+ dOpts = append ( dOpts , dockerconfigresolver . WithMaxConnsPerHost ( options . MaxConnsPerHost ))
171171 }
172-
173- resolverOpts := docker.ResolverOptions {
174- Tracker : pushTracker ,
175- Hosts : dockerconfig .ConfigureHosts (ctx , * ho ),
172+ if options .MaxIdleConns > 0 {
173+ dOpts = append (dOpts , dockerconfigresolver .WithMaxIdleConns (options .MaxIdleConns ))
174+ }
175+ if options .RequestTimeout > 0 {
176+ dOpts = append (dOpts , dockerconfigresolver .WithRequestTimeout (time .Duration (options .RequestTimeout )* time .Second ))
176177 }
178+ if options .MaxRetries > 0 {
179+ dOpts = append (dOpts , dockerconfigresolver .WithMaxRetries (options .MaxRetries ))
180+ }
181+ if options .RetryInitialDelay > 0 {
182+ dOpts = append (dOpts , dockerconfigresolver .WithRetryInitialDelay (time .Duration (options .RetryInitialDelay )* time .Millisecond ))
183+ }
184+ // Use the local push tracker for this operation
185+ dOpts = append (dOpts , dockerconfigresolver .WithTracker (pushTracker ))
177186
178- resolver := docker .NewResolver (resolverOpts )
187+ resolver , err := dockerconfigresolver .New (ctx , refDomain , dOpts ... )
188+ if err != nil {
189+ return err
190+ }
179191 if err = pushFunc (resolver ); err != nil {
180192 // In some circumstance (e.g. people just use 80 port to support pure http), the error will contain message like "dial tcp <port>: connection refused"
181193 if ! errors .Is (err , http .ErrSchemeMismatch ) && ! errutil .IsErrConnectionRefused (err ) {
@@ -184,6 +196,22 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
184196 if options .GOptions .InsecureRegistry {
185197 log .G (ctx ).WithError (err ).Warnf ("server %q does not seem to support HTTPS, falling back to plain HTTP" , refDomain )
186198 dOpts = append (dOpts , dockerconfigresolver .WithPlainHTTP (true ))
199+ // Apply same connection limits for HTTP fallback
200+ if options .MaxConnsPerHost > 0 {
201+ dOpts = append (dOpts , dockerconfigresolver .WithMaxConnsPerHost (options .MaxConnsPerHost ))
202+ }
203+ if options .MaxIdleConns > 0 {
204+ dOpts = append (dOpts , dockerconfigresolver .WithMaxIdleConns (options .MaxIdleConns ))
205+ }
206+ if options .RequestTimeout > 0 {
207+ dOpts = append (dOpts , dockerconfigresolver .WithRequestTimeout (time .Duration (options .RequestTimeout )* time .Second ))
208+ }
209+ if options .MaxRetries > 0 {
210+ dOpts = append (dOpts , dockerconfigresolver .WithMaxRetries (options .MaxRetries ))
211+ }
212+ if options .RetryInitialDelay > 0 {
213+ dOpts = append (dOpts , dockerconfigresolver .WithRetryInitialDelay (time .Duration (options .RetryInitialDelay )* time .Millisecond ))
214+ }
187215 resolver , err = dockerconfigresolver .New (ctx , refDomain , dOpts ... )
188216 if err != nil {
189217 return err
0 commit comments