elastic/elasticsearch · error · IllegalStateException

{className} can only be applied to the root project.

Error message

{className} can only be applied to the root project.

What it means

Thrown by DockerSupportPlugin.apply(Project) when the plugin is applied to a Gradle subproject instead of the root project. The plugin registers a Gradle shared build service (DockerSupportService) that must exist exactly once per build, so applying it to a non-root project would create duplicate or orphaned service registrations. The guard is a simple identity check: project != project.getRootProject().

Source

Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/docker/DockerSupportPlugin.java:43

/**
 * Plugin providing {@link DockerSupportService} for detecting Docker installations and determining requirements for Docker-based
 * Elasticsearch build tasks.
 */
public class DockerSupportPlugin implements Plugin<Project> {
    public static final String DOCKER_SUPPORT_SERVICE_NAME = "dockerSupportService";
    public static final String DOCKER_ON_LINUX_EXCLUSIONS_FILE = ".ci/dockerOnLinuxExclusions";

    private final ProjectLayout projectLayout;

    @Inject
    public DockerSupportPlugin(ProjectLayout projectLayout) {
        this.projectLayout = projectLayout;
    }

    @Override
    public void apply(Project project) {
        if (project != project.getRootProject()) {
            throw new IllegalStateException(this.getClass().getName() + " can only be applied to the root project.");
        }
        project.getPlugins().apply(GlobalBuildInfoPlugin.class);
        var buildParams = loadBuildParams(project).get();

        Provider<DockerSupportService> dockerSupportServiceProvider = project.getGradle()
            .getSharedServices()
            .registerIfAbsent(DOCKER_SUPPORT_SERVICE_NAME, DockerSupportService.class, spec -> spec.parameters(params -> {
                params.setExclusionsFile(projectLayout.getSettingsDirectory().file(DOCKER_ON_LINUX_EXCLUSIONS_FILE).getAsFile());
                params.getIsCI().set(buildParams.getCi());
            }));

        // Ensure that if we are trying to run Docker build tasks, we assert an available Docker installation exists
        project.getGradle().getTaskGraph().whenReady(graph -> {
            List<String> dockerTasks = graph.getAllTasks()
                .stream()
                .filter(task -> task instanceof DockerBuildTask)
                .map(Task::getPath)
                .collect(Collectors.toList());

View on GitHub (pinned to db6a809a66)

Solutions

  1. Apply DockerSupportPlugin only in the root project's build.gradle or in settings.gradle via the settings plugin mechanism.
  2. If applying through a convention plugin, ensure the convention plugin itself is only applied to the root project (guard with project == project.getRootProject() before applying DockerSupportPlugin).
  3. If you need Docker information in a subproject, access the shared service registered by the root project via project.getRootProject().getExtensions().findByType(...) or Gradle shared services lookup instead of re-applying the plugin.

Example fix

// before (subproject build.gradle)
plugins {
    id('elasticsearch.docker-support')
}

// after (root build.gradle only)
plugins {
    id('elasticsearch.docker-support')
}
// subprojects access Docker info via root-registered service
Defensive patterns

Strategy: validation

Validate before calling

// In a convention plugin, validate before applying
if (project == project.getRootProject()) {
    project.getPlugins().apply(DockerSupportPlugin.class);
} else {
    // access Docker service from root instead
}

Prevention

When it happens

Trigger: Calling project.getPlugins().apply(DockerSupportPlugin.class) (or applying it via the plugins {} block) inside a subproject's build.gradle rather than in the root settings.gradle or root build.gradle. Also triggered if a convention plugin that bundles DockerSupportPlugin is applied to a subproject.

Common situations: A build engineer adds Docker support to a specific subproject's build.gradle thinking it is scoped to that project. A convention plugin in build-tools-internal that transitively applies DockerSupportPlugin is applied per-subproject. A composite build includes the Elasticsearch workspace and the plugin is picked up by an included build's subproject.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/a3691ae6f71017d2. Report an issue: GitHub.