@@ -24,6 +24,7 @@ import (
24
24
"net/http"
25
25
"os"
26
26
"path/filepath"
27
+ "time"
27
28
28
29
"github.com/opencontainers/go-digest"
29
30
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -34,7 +35,6 @@ import (
34
35
"github.com/containerd/containerd/v2/core/images/converter"
35
36
"github.com/containerd/containerd/v2/core/remotes"
36
37
"github.com/containerd/containerd/v2/core/remotes/docker"
37
- dockerconfig "github.com/containerd/containerd/v2/core/remotes/docker/config"
38
38
"github.com/containerd/containerd/v2/pkg/reference"
39
39
"github.com/containerd/log"
40
40
"github.com/containerd/stargz-snapshotter/estargz"
@@ -165,17 +165,29 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
165
165
}
166
166
dOpts = append (dOpts , dockerconfigresolver .WithHostsDirs (options .GOptions .HostsDir ))
167
167
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 ))
171
171
}
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 ))
176
177
}
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 ))
177
186
178
- resolver := docker .NewResolver (resolverOpts )
187
+ resolver , err := dockerconfigresolver .New (ctx , refDomain , dOpts ... )
188
+ if err != nil {
189
+ return err
190
+ }
179
191
if err = pushFunc (resolver ); err != nil {
180
192
// 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"
181
193
if ! errors .Is (err , http .ErrSchemeMismatch ) && ! errutil .IsErrConnectionRefused (err ) {
@@ -184,6 +196,22 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
184
196
if options .GOptions .InsecureRegistry {
185
197
log .G (ctx ).WithError (err ).Warnf ("server %q does not seem to support HTTPS, falling back to plain HTTP" , refDomain )
186
198
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
+ }
187
215
resolver , err = dockerconfigresolver .New (ctx , refDomain , dOpts ... )
188
216
if err != nil {
189
217
return err
0 commit comments