You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OS: Windows [Windows 11 Home 24H2]
Docker: Docker Desktop [4.40.0] using WSL 2 backend (Ubuntu [Version 2])
Java: JDK 17 ([17.0.11])
docker-java Version: Tested with 3.3.6, 3.4.0, 3.5.0
Transport: docker-java-transport-httpclient5 (Issue persists across tested docker-java versions with this transport. Netty transport failed to resolve via Maven).
Docker Host URI (Detected by docker-java): npipe:////./pipe/dockerDesktopLinuxEngine
Problem Description:
When attempting to create an interactive terminal session using dockerClient.execCreateCmd(...).withTty(true).withAttachStdin(true)... followed by dockerClient.execStartCmd(...).exec(callback), the initial connection to the Docker daemon works (e.g., pingCmd succeeds), and the exec instance is created.
The ResultCallback.Adapter receives the onStart event and typically receives one or two onNext events containing the initial shell prompt data from the container's PTY.
However, immediately after receiving the first few bytes from the PTY stream, the connection is abruptly terminated, resulting in the onError callback being invoked with either:
java.io.IOException: An established connection was aborted by the software in your host computer (when running within a Spring Boot application using WebSockets to forward the stream)
java.io.IOException: java.io.IOException: Denna pipe har avslutats (This pipe has been terminated) (when running the minimal reproducible example below).
This happens consistently across different docker-java versions (3.3.6, 3.4.0, 3.5.0) when using the httpclient5 transport. Manual docker exec -it sh commands from the host terminal work without issue.
Troubleshooting Steps Taken:
Created a minimal reproducible example (code below) which isolates the issue outside of Spring Boot/WebSockets.
Tested docker-java versions 3.3.6, 3.4.0, and 3.5.0 with the httpclient5 transport – the PTY stream error persists.
Attempted to use the netty transport, but encountered Maven dependency resolution issues preventing its download.
Reinstalled Docker Desktop.
Ensured WSL 2 integration is enabled for the default distro (Ubuntu).
Disabled Windows Defender Firewall and all third-party antivirus software.
Tried connecting via the default named pipe and explicitly via tcp://localhost:2375 (after enabling insecure TCP exposure in Docker Desktop). While TCP allowed the initial pingCmd to succeed when the named pipe failed, the subsequent PTY stream still failed with the "Connection aborted" error. TCP exposure has since been disabled.
private static final Logger logger = LoggerFactory.getLogger(DockerTest.class);
public static void main(String[] args) {
DockerClient dockerClient = null;
String containerId = null;
PipedOutputStream ptyStdin = null; // For potential input later
PipedInputStream ptyStdinPipe = null; // For potential input later
try {
// --- 1. Initialize Docker Client ---
logger.info("Attempting to initialize Docker client using default host detection...");
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
logger.info("DockerClientConfig resolved DOCKER_HOST to: {}", config.getDockerHost());
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
.dockerHost(config.getDockerHost())
.sslConfig(config.getSSLConfig())
.connectionTimeout(Duration.ofSeconds(30))
.responseTimeout(Duration.ofSeconds(45))
.build();
dockerClient = DockerClientImpl.getInstance(config, httpClient);
logger.info("Pinging Docker daemon...");
dockerClient.pingCmd().exec();
logger.info("Docker Ping Successful!");
// --- 2. Create and Start a Simple Container ---
String imageName = "ubuntu:latest"; // Use a simple image
logger.info("Pulling image {} if not present...", imageName);
try {
dockerClient.inspectImageCmd(imageName).exec();
logger.info("Image {} already present.", imageName);
} catch (Exception e) { // Catch generic Exception as NotFoundException might not be the only cause
logger.info("Image {} not found or error inspecting, pulling...", imageName);
dockerClient.pullImageCmd(imageName).start().awaitCompletion();
logger.info("Image {} pulled.", imageName);
}
logger.info("Creating container from image {}...", imageName);
HostConfig hostConfig = HostConfig.newHostConfig().withNetworkMode("none"); // No network needed
CreateContainerResponse container = dockerClient.createContainerCmd(imageName)
.withHostConfig(hostConfig)
.withCmd("sleep", "infinity") // Keep container running
.withTty(true) // Keep TTY for potential exec later
.withAttachStdin(false) // Stdin attached later via exec
.withAttachStdout(true)
.withAttachStderr(true)
.exec();
containerId = container.getId();
logger.info("Created container: {}", containerId);
dockerClient.startContainerCmd(containerId).exec();
logger.info("Started container: {}", containerId);
// --- 3. Create Exec Instance (Shell) ---
logger.info("Creating exec instance in container {}...", containerId);
ExecCreateCmdResponse execCreateResponse = dockerClient.execCreateCmd(containerId)
.withAttachStdout(true)
.withAttachStderr(true)
.withAttachStdin(true) // Attach stdin
.withTty(true) // Allocate TTY
.withCmd("sh", "-i") // Interactive shell
.exec();
String execId = execCreateResponse.getId();
logger.info("Created exec instance: {}", execId);
// --- 4. Start Exec and Attach Streams ---
// Setup pipes for stdin
ptyStdin = new PipedOutputStream();
ptyStdinPipe = new PipedInputStream(ptyStdin);
logger.info("Starting exec instance {} and attaching streams...", execId);
// Create final references for use in the inner class
final PipedInputStream finalPtyStdinPipe = ptyStdinPipe;
final PipedOutputStream finalPtyStdin = ptyStdin;
ResultCallback.Adapter<Frame> callback = new ResultCallback.Adapter<Frame>() {
@Override
public void onStart(Closeable closeable) {
logger.info("[EXEC CALLBACK] onStart");
}
@Override
public void onNext(Frame frame) {
logger.info("[EXEC CALLBACK] onNext: Type={}, Payload={}",
frame.getStreamType(),
new String(frame.getPayload(), StandardCharsets.UTF_8).trim());
// Simulate the point where the original error occurred
// If the connection aborts shortly after this log, it matches the pattern
}
@Override
public void onError(Throwable throwable) {
logger.error("[EXEC CALLBACK] onError:", throwable);
}
@Override
public void onComplete() {
logger.info("[EXEC CALLBACK] onComplete");
}
@Override
public void close() throws IOException {
logger.info("[EXEC CALLBACK] close");
if (finalPtyStdinPipe != null) finalPtyStdinPipe.close(); // Use final reference
if (finalPtyStdin != null) finalPtyStdin.close(); // Use final reference
}
};
// Start the exec command
dockerClient.execStartCmd(execId)
.withDetach(false)
.withTty(true)
.withStdIn(ptyStdinPipe) // Attach the input stream
.exec(callback);
logger.info("Exec instance started. Waiting for callback events...");
// Keep the main thread alive briefly to see callbacks
Thread.sleep(15000); // Wait 15 seconds
logger.info("Minimal test finished waiting.");
} catch (Exception e) {
logger.error("An error occurred during the test:", e);
} finally {
// --- 5. Cleanup ---
logger.info("Starting cleanup...");
// Close streams using original variables (which might be null if setup failed)
if (ptyStdin != null) try { ptyStdin.close(); } catch (IOException e) { /* ignore */ }
if (ptyStdinPipe != null) try { ptyStdinPipe.close(); } catch (IOException e) { /* ignore */ }
if (dockerClient != null) {
if (containerId != null) {
try {
logger.info("Stopping container {}...", containerId);
dockerClient.stopContainerCmd(containerId).withTimeout(5).exec();
} catch (Exception e) {
logger.warn("Error stopping container {}: {}", containerId, e.getMessage());
}
try {
logger.info("Removing container {}...", containerId);
dockerClient.removeContainerCmd(containerId).withForce(true).exec();
} catch (Exception e) {
logger.warn("Error removing container {}: {}", containerId, e.getMessage());
}
}
try {
logger.info("Closing Docker client...");
dockerClient.close();
} catch (IOException e) {
logger.error("Error closing Docker client:", e);
}
}
logger.info("Cleanup finished.");
}
}
}
Relevant Logs from Minimal Test:
[docker-java-stream-...] INFO com.example.test.DockerTest - [EXEC CALLBACK] onStart
[docker-java-stream-...] INFO com.example.test.DockerTest - [EXEC CALLBACK] onNext: Type=RAW, Payload=#
[docker-java-stream-...] INFO com.example.test.DockerTest - [EXEC CALLBACK] onNext: Type=RAW, Payload=
[docker-java-stream-...] ERROR com.example.test.DockerTest - [EXEC CALLBACK] onError:
java.io.IOException: java.io.IOException: Denna pipe har avslutats
at java.base/java.nio.channels.Channels$2.read(Channels.java:240)
at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.read(SessionInputBufferImpl.java:195)
... (stack trace continues) ...
Caused by: java.io.IOException: Denna pipe har avslutats
at java.base/sun.nio.ch.Iocp.translateErrorToIOException(Iocp.java:299)
... (stack trace continues) ...
[docker-java-stream-...] INFO com.example.test.DockerTest - [EXEC CALLBACK] onComplete
Expected Behavior:
The execStartCmd callback should remain active, receiving further output from the container's shell as commands are (theoretically) sent via the ptyStdin stream. It should only call onComplete or close when the shell process inside the container exits or the Closeable from onStart is closed.
Actual Behavior:
The connection underlying the PTY stream is terminated by the host system almost immediately after the stream starts, triggering onError.
The text was updated successfully, but these errors were encountered:
Environment:
OS: Windows [Windows 11 Home 24H2]
Docker: Docker Desktop [4.40.0] using WSL 2 backend (Ubuntu [Version 2])
Java: JDK 17 ([17.0.11])
docker-java Version: Tested with 3.3.6, 3.4.0, 3.5.0
Transport: docker-java-transport-httpclient5 (Issue persists across tested docker-java versions with this transport. Netty transport failed to resolve via Maven).
Docker Host URI (Detected by docker-java): npipe:////./pipe/dockerDesktopLinuxEngine
Problem Description:
When attempting to create an interactive terminal session using dockerClient.execCreateCmd(...).withTty(true).withAttachStdin(true)... followed by dockerClient.execStartCmd(...).exec(callback), the initial connection to the Docker daemon works (e.g., pingCmd succeeds), and the exec instance is created.
The ResultCallback.Adapter receives the onStart event and typically receives one or two onNext events containing the initial shell prompt data from the container's PTY.
However, immediately after receiving the first few bytes from the PTY stream, the connection is abruptly terminated, resulting in the onError callback being invoked with either:
java.io.IOException: An established connection was aborted by the software in your host computer (when running within a Spring Boot application using WebSockets to forward the stream)
java.io.IOException: java.io.IOException: Denna pipe har avslutats (This pipe has been terminated) (when running the minimal reproducible example below).
This happens consistently across different docker-java versions (3.3.6, 3.4.0, 3.5.0) when using the httpclient5 transport. Manual docker exec -it sh commands from the host terminal work without issue.
Troubleshooting Steps Taken:
Created a minimal reproducible example (code below) which isolates the issue outside of Spring Boot/WebSockets.
Tested docker-java versions 3.3.6, 3.4.0, and 3.5.0 with the httpclient5 transport – the PTY stream error persists.
Attempted to use the netty transport, but encountered Maven dependency resolution issues preventing its download.
Reinstalled Docker Desktop.
Ensured WSL 2 integration is enabled for the default distro (Ubuntu).
Disabled Windows Defender Firewall and all third-party antivirus software.
Tried connecting via the default named pipe and explicitly via tcp://localhost:2375 (after enabling insecure TCP exposure in Docker Desktop). While TCP allowed the initial pingCmd to succeed when the named pipe failed, the subsequent PTY stream still failed with the "Connection aborted" error. TCP exposure has since been disabled.
Minimal Reproducible Example (DockerTest.java):
package com.example.test;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
public class DockerTest {
}
Relevant Logs from Minimal Test:
[docker-java-stream-...] INFO com.example.test.DockerTest - [EXEC CALLBACK] onStart
[docker-java-stream-...] INFO com.example.test.DockerTest - [EXEC CALLBACK] onNext: Type=RAW, Payload=#
[docker-java-stream-...] INFO com.example.test.DockerTest - [EXEC CALLBACK] onNext: Type=RAW, Payload=
[docker-java-stream-...] ERROR com.example.test.DockerTest - [EXEC CALLBACK] onError:
java.io.IOException: java.io.IOException: Denna pipe har avslutats
at java.base/java.nio.channels.Channels$2.read(Channels.java:240)
at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.read(SessionInputBufferImpl.java:195)
... (stack trace continues) ...
Caused by: java.io.IOException: Denna pipe har avslutats
at java.base/sun.nio.ch.Iocp.translateErrorToIOException(Iocp.java:299)
... (stack trace continues) ...
[docker-java-stream-...] INFO com.example.test.DockerTest - [EXEC CALLBACK] onComplete
Expected Behavior:
The execStartCmd callback should remain active, receiving further output from the container's shell as commands are (theoretically) sent via the ptyStdin stream. It should only call onComplete or close when the shell process inside the container exits or the Closeable from onStart is closed.
Actual Behavior:
The connection underlying the PTY stream is terminated by the host system almost immediately after the stream starts, triggering onError.
The text was updated successfully, but these errors were encountered: