@@ -94,7 +94,8 @@ func reconcile(_ context.Context, project *types.Project, observed *ObservedStat
9494
9595// reconcileNetworks adds plan nodes for network creation or recreation.
9696func (r * reconciler ) reconcileNetworks () error {
97- for key , desired := range r .project .Networks {
97+ for _ , key := range sortedKeys (r .project .Networks ) {
98+ desired := r .project .Networks [key ]
9899 if desired .External {
99100 continue
100101 }
@@ -187,7 +188,8 @@ func (r *reconciler) planRecreateNetwork(key string, nw *types.NetworkConfig) er
187188
188189// reconcileVolumes adds plan nodes for volume creation or recreation.
189190func (r * reconciler ) reconcileVolumes () error {
190- for key , desired := range r .project .Volumes {
191+ for _ , key := range sortedKeys (r .project .Volumes ) {
192+ desired := r .project .Volumes [key ]
191193 if desired .External {
192194 continue
193195 }
@@ -285,10 +287,11 @@ func (r *reconciler) planRecreateVolume(key string, vol *types.VolumeConfig) {
285287}
286288
287289// servicesUsingNetwork returns the names of services that reference the given
288- // compose network key.
290+ // compose network key, sorted for deterministic plan output .
289291func (r * reconciler ) servicesUsingNetwork (networkKey string ) []string {
290292 var names []string
291- for _ , svc := range r .project .Services {
293+ for _ , key := range sortedKeys (r .project .Services ) {
294+ svc := r .project .Services [key ]
292295 if _ , ok := svc .Networks [networkKey ]; ok {
293296 names = append (names , svc .Name )
294297 }
@@ -297,10 +300,11 @@ func (r *reconciler) servicesUsingNetwork(networkKey string) []string {
297300}
298301
299302// servicesUsingVolume returns the names of services that mount the given
300- // compose volume key.
303+ // compose volume key, sorted for deterministic plan output .
301304func (r * reconciler ) servicesUsingVolume (volumeKey string ) []string {
302305 var names []string
303- for _ , svc := range r .project .Services {
306+ for _ , key := range sortedKeys (r .project .Services ) {
307+ svc := r .project .Services [key ]
304308 for _ , v := range svc .Volumes {
305309 if v .Source == volumeKey {
306310 names = append (names , svc .Name )
@@ -338,10 +342,13 @@ func (r *reconciler) reconcileContainers() error {
338342// dependencies are reconciled before the services that depend on them.
339343func (r * reconciler ) visitInDependencyOrder (g * Graph ) error {
340344 visited := map [string ]bool {}
345+ // Sort vertex keys for deterministic plan output in tests
346+ keys := sortedKeys (g .Vertices )
341347 for {
342348 // Find a vertex whose all children are visited
343349 var next * Vertex
344- for _ , v := range g .Vertices {
350+ for _ , k := range keys {
351+ v := g .Vertices [k ]
345352 if visited [v .Key ] {
346353 continue
347354 }
@@ -501,7 +508,7 @@ func (r *reconciler) mustRecreate(expected types.ServiceConfig, oc ObservedConta
501508
502509// hasNetworkMismatch checks if the container is not connected to all expected networks.
503510func (r * reconciler ) hasNetworkMismatch (expected types.ServiceConfig , oc ObservedContainer ) bool {
504- for net := range expected .Networks {
511+ for _ , net := range sortedKeys ( expected .Networks ) {
505512 expectedID := ""
506513 if obs , ok := r .observed .Networks [net ]; ok {
507514 expectedID = obs .ID
@@ -636,7 +643,8 @@ func (r *reconciler) planStopDependents(service types.ServiceConfig) []*PlanNode
636643// references, plus the last node of dependency services.
637644func (r * reconciler ) infrastructureDeps (service types.ServiceConfig ) []* PlanNode {
638645 var deps []* PlanNode
639- for net := range service .Networks {
646+ // Sort map keys for deterministic plan output in tests
647+ for _ , net := range sortedKeys (service .Networks ) {
640648 if node , ok := r .networkNodes [net ]; ok {
641649 deps = append (deps , node )
642650 }
@@ -648,7 +656,7 @@ func (r *reconciler) infrastructureDeps(service types.ServiceConfig) []*PlanNode
648656 }
649657 }
650658 }
651- for depName := range service .DependsOn {
659+ for _ , depName := range sortedKeys ( service .DependsOn ) {
652660 if node , ok := r .serviceNodes [depName ]; ok {
653661 deps = append (deps , node )
654662 }
@@ -704,5 +712,16 @@ func (r *reconciler) observedSummaries(serviceName string) []container.Summary {
704712 return result
705713}
706714
715+ // sortedKeys returns the keys of a map sorted alphabetically.
716+ // This ensures deterministic iteration order for reproducible plan output in tests.
717+ func sortedKeys [V any ](m map [string ]V ) []string {
718+ keys := make ([]string , 0 , len (m ))
719+ for k := range m {
720+ keys = append (keys , k )
721+ }
722+ sort .Strings (keys )
723+ return keys
724+ }
725+
707726// serviceLabel is a package-level shorthand for the service label key.
708727const serviceLabel = "com.docker.compose.service"
0 commit comments