Anvil
Building blocksEnvironmentsConfiguration

Execution providers

Run host processes by default or configure a local Docker daemon through an embedded engine.

Local execution is the default and the normal Gradle workflow. Anvil also includes a Docker execution provider for applications that construct the engine with explicit image settings. The selected provider applies to the whole scenario.

Choose an execution provider

ChoiceRequirementsBehavior
localCompatible local or provisioned JavaStarts JVMs on the host with allocated listener ports
dockerLocal Docker daemon, accessible workspace paths, configured Java imagesStarts containers on a scenario network and publishes host endpoints

Set .executionProviderId("local") or .executionProviderId("docker") on an AnvilScenario to override the engine's executionId. Selecting Docker alone is insufficient: the service-discovered provider has no image mappings, and the Gradle DSL does not currently expose image configuration.

Configure the embedded Docker path

Use the embedding dependencies, then add the Docker client dependencies to your application build. The launcher contains the execution implementation; its embedded Docker module does not bring these client libraries into the assembly:

dependencies {
	runtimeOnly("com.github.docker-java:docker-java:3.7.1")
	runtimeOnly("com.github.docker-java:docker-java-transport-zerodep:3.7.1")
}

3.7.1 is the Docker client version in the current Anvil dependency catalog. Keep these dependencies aligned with the Anvil release you embed.

Place this helper at src/main/java/DockerScenario.java in your application. Pass an existing scenario whose servers use Java 21, plus a verified immutable Java 21 image reference from your own image selection:

import me.whereareiam.anvil.api.model.EngineOptions;
import me.whereareiam.anvil.api.model.java.JavaRequirement;
import me.whereareiam.anvil.api.model.java.JavaSelection;
import me.whereareiam.anvil.api.model.scenario.AnvilScenario;
import me.whereareiam.anvil.api.scenario.ScenarioContext;
import me.whereareiam.anvil.api.scenario.ScenarioEngine;
import me.whereareiam.anvil.environment.execution.docker.execution.DockerExecutionProvider;
import me.whereareiam.anvil.environment.execution.docker.execution.DockerExecutionSettings;
import me.whereareiam.anvil.launcher.AnvilLauncher;

public final class DockerScenario {
	public static void run(AnvilScenario scenario, String java21Image) {
		var docker = new DockerExecutionProvider(DockerExecutionSettings.builder()
				.image("temurin:21", java21Image)
				.build());
		var options = EngineOptions.builder()
				.eulaAccepted(true)
				.protocolLibrary("mcprotocol")
				.executionProviderId("docker")
				.javaSelection(JavaSelection.builder()
						.requirement(JavaRequirement.builder()
								.featureVersion(21)
								.distribution("temurin")
								.build())
						.build())
				.build();

		try (ScenarioEngine engine = AnvilLauncher.create(options, docker);
		     ScenarioContext context = engine.start(scenario.toBuilder().executionProviderId("docker").build())) {
			System.out.println(context.processes().get(scenario.getEntrypoint()).address());
		}
	}
}

Call the helper from the application's entry point. Supply an image such as your-registry/your-jdk@sha256:<verified-image-digest>, replacing the entire placeholder with an actual reference. The mapped image must contain a java executable of exactly that feature version and support the mounted scenario workspace. Map every LTS release your processes plan, such as temurin:17 for a Paper 1.18.2 server or temurin:25 for 26.1.2; a requirement without a distribution uses the temurin key. A missing mapping fails before any container starts and lists the configured keys. Process or scenario Java overrides still take precedence over the helper's engine default.

Success means the processes and agents became ready and an entrypoint address was returned. This helper closes the environment immediately afterward; perform your actions inside the context lifetime to use it. Docker scenarios need the same explicit platform, agent, protocol, and capability artifacts as local scenarios.

Understand the current limits

The Docker daemon must be local and able to mount the host workspace. Unix sockets and Windows named pipes are accepted; remote TCP DOCKER_HOST endpoints are rejected. Host Java sources are rejected. Images are inspected to contain exactly the planned Java feature version, and their resolved identities are retained in the cache. Use immutable digest references when repeatability across fresh caches matters.

For a proxy scenario, setting backendNetworkExposure to PRIVATE suppresses published backend game ports. Combining it with networkServerAccess(PROXY_ONLY) creates an internal scenario Docker network. These are NetworkPolicy builder settings under me.whereareiam.anvil.api.model; their enums live in me.whereareiam.anvil.api.type.network. Local execution rejects that strict combination.

All scenario containers still share a network; this is not a separate access-control rule between each backend and each proxy. Loopback host binding restricts host exposure, and does not isolate processes from other programs on the same machine. Docker agents bind inside their containers and are published to host loopback with per-run authentication.

Docker forwards a published game port through its own network, so a container sees the bridge gateway instead of the address a client connected from. A player's sourceAddress is therefore refused under Docker execution; an execution provider declares whether it preserves client addresses through ExecutionProvider.preservesClientAddress().

Keep normal automated scenarios on loopback. A non-loopback game listener requires the explicit manual LAN settings.

On this page