docker/cli · error

invalid image reference for service

Error message

invalid image reference for service %s: no image specified

What it means

Raised by loadComposeFile when a service has no image defined (svc.Image == ""). Swarm services require an image reference because they cannot build; the error fires at loader.go:57 before the spec is ever sent to the daemon.

Solutions

  1. Add an `image:` to the service in compose.yml (preferably the same tag you build and push).
  2. Build and push the image in CI, then reference the pushed tag in `image:`.
  3. If using multi-stage, ensure the final image reference is set.
  4. Validate with `docker compose config` to confirm image is populated.

Example fix

# before
services:
  web:
    build: .
# after
services:
  web:
    image: myregistry/web:1.0.0
    build: .
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every service has an image before stack deploy
for _, svc := range config.Services {
    if svc.Image == "" {
        return fmt.Errorf("service %q needs an image for swarm deploy", svc.Name)
    }
}

Type guard

type DeployableService struct {
    Name  string
    Image string // required, non-empty
}

func isDeployable(s composetypes.ServiceConfig) bool {
    return strings.TrimSpace(s.Image) != ""
}

Prevention

When it happens

Trigger: A compose service using only `build:` without `image:`, deployed via `docker stack deploy`. Swarm cannot build images, so each service must declare an image. The loop at loader.go:55 checks every service's Image field.

Common situations: Using a compose file built for `docker compose up` (where build-only is fine) and pointing `docker stack deploy` at it; forgetting to add image: after adding build:; CI that builds and pushes images but the compose file wasn't updated to reference them.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/90fc68f5bf2d7f09. Report an issue: GitHub.

Appendix: source

Thrown at cli/command/stack/loader.go:57

	}

	dicts := getDictsFrom(configDetails.ConfigFiles)
	unsupportedProperties := loader.GetUnsupportedProperties(dicts...)
	if len(unsupportedProperties) > 0 {
		_, _ = fmt.Fprintf(streams.Err(), "Ignoring unsupported options: %s\n\n",
			strings.Join(unsupportedProperties, ", "))
	}

	deprecatedProperties := loader.GetDeprecatedProperties(dicts...)
	if len(deprecatedProperties) > 0 {
		_, _ = fmt.Fprintf(streams.Err(), "Ignoring deprecated options:\n\n%s\n\n",
			propertyWarnings(deprecatedProperties))
	}

	// Validate if each service has a valid image-reference.
	for _, svc := range config.Services {
		if svc.Image == "" {
			return nil, fmt.Errorf("invalid image reference for service %s: no image specified", svc.Name)
		}
		if _, err := reference.ParseAnyReference(svc.Image); err != nil {
			return nil, fmt.Errorf("invalid image reference for service %s: %w", svc.Name, err)
		}
	}

	return config, nil
}

func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]any {
	dicts := make([]map[string]any, 0, len(configFiles))
	for _, configFile := range configFiles {
		dicts = append(dicts, configFile.Config)
	}

	return dicts
}

View on GitHub (pinned to 4f84911bfe)