2727public class WindowsDockerProvider extends DockerProvider {
2828 private static final Logger log = LoggerFactory .getLogger (WindowsDockerProvider .class );
2929
30- private static final String DOCKER_DOWNLOAD_URL = "https://download.docker.com/win/static/stable/%s/%s.zip" ;
30+ private static final String DOCKER_DOWNLOAD_URL = "https://download.docker.com/win/static/stable/%s/docker- %s.zip" ;
3131 private static final Path DOCKER_PATH = DOCKER_DIR .resolve ("docker/dockerd.exe" );
3232 private static final Path DOCKER_VERSION_FILE = DOCKER_DIR .resolve (".docker-version" );
3333
@@ -107,7 +107,6 @@ private void ensureWsl2DockerInstalled() throws IOException {
107107 log .info ("Checking Docker installation in WSL2 (distro: {})..." , wslDistro );
108108
109109 try {
110- // Check if dockerd exists
111110 ProcessBuilder checkPb = new ProcessBuilder ("wsl" , "-d" , wslDistro , "-e" , "bash" , "-c" , "command -v dockerd" );
112111 checkPb .redirectErrorStream (true );
113112 Process checkProcess = checkPb .start ();
@@ -241,19 +240,15 @@ private void startNativeDocker() throws IOException, InterruptedException {
241240 log .info ("Docker daemon started (instance: {}, pipe: {})" , instanceId , pipeName );
242241 }
243242
244- // Port for this instance's Docker daemon (base port + hash of instance ID)
245243 private int dockerPort ;
246244
247245 private void startWsl2Docker () throws IOException , InterruptedException {
248246 ensureInstalled ();
249247
250- // Use TCP instead of Unix socket so Windows Java can connect
251- // Generate a unique port based on instance ID to avoid conflicts
252248 dockerPort = 2375 + Math .abs (instanceId .hashCode () % 1000 );
253- wslSocketPath = "tcp://0.0.0.0:" + dockerPort ; // Listen on all interfaces inside WSL
249+ wslSocketPath = "tcp://0.0.0.0:" + dockerPort ;
254250 String wslLogFile = "/tmp/docker-java-" + instanceId + ".log" ;
255251
256- // Get the actual home directory path in WSL
257252 String wslHome = runWslCommand ("echo $HOME" , false , 5 );
258253 if (wslHome .isEmpty ()) {
259254 wslHome = "/home/" + runWslCommand ("whoami" , false , 5 );
@@ -264,24 +259,19 @@ private void startWsl2Docker() throws IOException, InterruptedException {
264259 String wslExecDir = wslHome + "/.docker-java/instances/" + instanceId + "/exec" ;
265260 String wslPidFile = wslHome + "/.docker-java/instances/" + instanceId + "/docker.pid" ;
266261
267- // Create directories in WSL2
268262 log .debug ("Creating directories in WSL2 (distro: {})..." , wslDistro );
269263 String mkdirResult = runWslCommand ("mkdir -p " + wslDataDir + " " + wslExecDir + " && echo ok" , false , 10 );
270264 log .debug ("mkdir result: {}" , mkdirResult );
271265
272- // Remove any existing socket
273266 runWslCommand ("rm -f " + wslSocketPath , false , 5 );
274267
275- // Check if Docker Desktop daemon is running and might conflict
276268 String existingDocker = runWslCommand ("pgrep -f 'dockerd' 2>/dev/null" , false , 5 );
277269 if (!existingDocker .isEmpty ()) {
278270 log .warn ("Another dockerd process may be running (PIDs: {}). This might cause conflicts." , existingDocker .replace ("\n " , ", " ));
279271 }
280272
281273 log .info ("Starting Docker daemon in WSL2 (instance: {}, distro: {})..." , instanceId , wslDistro );
282274
283- // dockerd always needs root to start (docker group only helps with client access)
284- // Check if passwordless sudo is configured
285275 if (!checkPasswordlessSudo ()) {
286276 log .error ("Starting dockerd requires root privileges." );
287277 log .error ("Please run this ONE-TIME setup in WSL ({}):" , wslDistro );
@@ -296,36 +286,28 @@ private void startWsl2Docker() throws IOException, InterruptedException {
296286
297287 log .debug ("Passwordless sudo for dockerd is available" );
298288
299- // Check if another dockerd is running (like Docker Desktop)
300289 String existingDockerds = runWslCommand ("pgrep -x dockerd 2>/dev/null | wc -l" , false , 5 ).trim ();
301290 boolean otherDockerdRunning = !"0" .equals (existingDockerds ) && !existingDockerds .isEmpty ();
302291
303- // Determine isolation flags to avoid conflicts with other Docker daemons
304292 String isolationFlags = "" ;
305293 if (otherDockerdRunning ) {
306294 log .info ("Another Docker daemon detected, using isolation flags to avoid conflicts" );
307- // Use --iptables=false to prevent conflicts with existing Docker's iptables rules
308- // Use --bridge=none to disable default bridge (avoids need to create custom bridge device)
309295 isolationFlags = " --iptables=false --bridge=none" ;
310296 }
311297
312298 log .debug ("Starting dockerd directly..." );
313299
314- // Build the dockerd command - use TCP so Windows Java can connect
315300 String dockerdCmd = String .format (
316301 "sudo dockerd -H %s --data-root %s --exec-root %s --pidfile %s%s" ,
317302 wslSocketPath , wslDataDir , wslExecDir , wslPidFile , isolationFlags );
318303
319304 log .debug ("Docker command: {}" , dockerdCmd );
320305
321- // First, test if the command works by running dockerd --version
322306 String versionCheck = runWslCommand ("sudo dockerd --version 2>&1" , false , 10 );
323307 log .debug ("dockerd version check: {}" , versionCheck );
324308
325- // Clear any old log file
326309 runWslCommand ("rm -f " + wslLogFile , false , 5 );
327310
328- // Start dockerd directly, capturing output
329311 log .debug ("Executing dockerd command..." );
330312 ProcessBuilder pb = new ProcessBuilder (
331313 "wsl" , "-d" , wslDistro , "--" , "bash" , "-c" ,
@@ -334,74 +316,60 @@ private void startWsl2Docker() throws IOException, InterruptedException {
334316 );
335317 pb .redirectErrorStream (true );
336318
337- // Start the process
338319 dockerProcess = pb .start ();
339320
340- // Give it time to initialize (Docker 29+ has a deliberate 1s+ delay for security warnings)
341321 Thread .sleep (8000 );
342322
343- // Check if process is still running
344323 if (!dockerProcess .isAlive ()) {
345324 byte [] output = readAllBytes (dockerProcess .getInputStream ());
346325 String outputStr = new String (output ).trim ();
347326 log .error ("WSL process died. Output: {}" , outputStr .isEmpty () ? "(empty)" : outputStr );
348327
349- // Check log file
350328 String logContent = runWslCommand ("cat " + wslLogFile + " 2>/dev/null || echo '(no log)'" , false , 5 );
351329 log .error ("Log file contents:\n {}" , logContent );
352330
353331 throw new RuntimeException ("dockerd failed to start. Check logs above." );
354332 }
355333
356- // Check if dockerd is actually running inside WSL
357334 String dockerdCheck = runWslCommand ("pgrep -x dockerd && echo 'running' || echo 'not running'" , false , 5 );
358335 log .debug ("dockerd process check: {}" , dockerdCheck .trim ());
359336
360- // Check log file for any startup messages
361337 String earlyLog = runWslCommand ("cat " + wslLogFile + " 2>/dev/null | head -20" , false , 5 );
362338 if (!earlyLog .isEmpty ()) {
363339 log .debug ("Early log output:\n {}" , earlyLog );
364340 }
365341
366- // Check if our specific dockerd is running (by port)
367342 String ourDockerd = runWslCommand ("pgrep -af 'dockerd.*" + dockerPort + "' 2>/dev/null || echo 'not found'" , false , 5 );
368343 log .debug ("Our dockerd process (by port {}): {}" , dockerPort , ourDockerd .trim ());
369344
370345 log .debug ("WSL process is alive, waiting for port {}..." , dockerPort );
371346
372347 if (!waitForWslSocket ()) {
373- // Additional diagnostics when connection fails
374348 String portCheck = runWslCommand ("ss -tlnp 2>/dev/null | grep " + dockerPort + " || echo 'port not found'" , false , 5 );
375349 log .error ("Port {} status after wait: {}" , dockerPort , portCheck .trim ());
376- // Try to get error log
377350 String logContent = runWslCommand ("cat " + wslLogFile + " 2>/dev/null" , false , 5 );
378351 if (!logContent .isEmpty ()) {
379352 log .error ("Docker daemon log:\n {}" , logContent );
380353 } else {
381354 log .error ("Docker daemon log is empty." );
382355 }
383356
384- // Check if port is listening INSIDE WSL
385357 String netstatCheck = runWslCommand ("ss -tlnp 2>/dev/null | grep " + dockerPort + " || echo 'port not listening in WSL'" , false , 5 );
386358 log .error ("Port {} status inside WSL: {}" , dockerPort , netstatCheck .trim ());
387359
388- // Check if dockerd is running inside WSL
389360 String dockerdPs = runWslCommand ("ps aux | grep dockerd | grep -v grep || echo 'no dockerd process'" , false , 5 );
390361 log .error ("dockerd processes in WSL:\n {}" , dockerdPs );
391362
392- // Check if our dockerd process exists (look for our port)
393363 String psOutput = runWslCommand ("pgrep -af 'dockerd.*" + dockerPort + "' 2>/dev/null || echo '(none with our port)'" , false , 5 );
394364 log .error ("Our dockerd process: {}" , psOutput );
395365
396- // Check if Docker Desktop's dockerd is blocking
397366 String allDockerds = runWslCommand ("pgrep -af dockerd 2>/dev/null || echo '(none)'" , false , 5 );
398367 if (allDockerds .contains ("-H fd://" )) {
399368 log .error ("" );
400369 log .error ("Docker Desktop's dockerd is running in WSL2." );
401370 log .error ("Stop it with: wsl -d {} -- sudo systemctl stop docker" , wslDistro );
402371 }
403372
404- // Check if our WSL process is still running
405373 if (dockerProcess != null && dockerProcess .isAlive ()) {
406374 log .error ("WSL process is still running but port not accessible from Windows" );
407375 log .error ("This might be a WSL2 networking issue. Try: wsl --shutdown" );
@@ -419,9 +387,7 @@ private void startWsl2Docker() throws IOException, InterruptedException {
419387 */
420388 private boolean checkPasswordlessSudo () {
421389 try {
422- // Try sudo -n (non-interactive) to check if dockerd can be run without password
423- // We check with 'sudo -n dockerd --version' to verify dockerd specifically
424- ProcessBuilder pb = new ProcessBuilder ("wsl" , "-d" , wslDistro , "-e" , "bash" , "-c" ,
390+ ProcessBuilder pb = new ProcessBuilder ("wsl" , "-d" , wslDistro , "-e" , "bash" , "-c" ,
425391 "sudo -n dockerd --version >/dev/null 2>&1 && echo yes || echo no" );
426392 pb .redirectErrorStream (true );
427393 Process process = pb .start ();
@@ -506,12 +472,10 @@ private boolean isAdministrator() {
506472
507473 private boolean isWsl2Available () {
508474 try {
509- // First check: try wsl -l -v which lists distros with their WSL version
510475 ProcessBuilder pb = new ProcessBuilder ("wsl" , "-l" , "-v" );
511476 pb .redirectErrorStream (true );
512477 Process process = pb .start ();
513478
514- // Read as bytes and convert, handling potential UTF-16 encoding
515479 byte [] outputBytes = readAllBytes (process .getInputStream ());
516480 String output = new String (outputBytes , java .nio .charset .StandardCharsets .UTF_16LE );
517481
@@ -523,7 +487,6 @@ private boolean isWsl2Available() {
523487 return false ;
524488 }
525489
526- // Parse the output to find a usable distro (not docker-desktop or docker-desktop-data)
527490 String [] lines = output .split ("\n " );
528491 String defaultDistro = null ;
529492 String usableDistro = null ;
@@ -534,15 +497,13 @@ private boolean isWsl2Available() {
534497 continue ;
535498 }
536499
537- // Check for WSL2 distro (has "2" in version column)
538500 if (!line .contains ("2" )) {
539501 continue ;
540502 }
541503
542504 boolean isDefault = line .startsWith ("*" );
543505 String distroName = line .replace ("*" , "" ).trim ().split ("\\ s+" )[0 ];
544506
545- // Skip Docker Desktop's internal distros
546507 if (distroName .toLowerCase ().startsWith ("docker-desktop" )) {
547508 log .debug ("Skipping Docker Desktop distro: {}" , distroName );
548509 if (isDefault ) {
@@ -551,7 +512,6 @@ private boolean isWsl2Available() {
551512 continue ;
552513 }
553514
554- // Found a usable distro
555515 if (usableDistro == null || isDefault ) {
556516 usableDistro = distroName ;
557517 log .debug ("Found usable WSL2 distro: {} (default: {})" , distroName , isDefault );
@@ -562,7 +522,6 @@ private boolean isWsl2Available() {
562522 wslDistro = usableDistro ;
563523 log .info ("Using WSL2 distro: {}" , wslDistro );
564524
565- // Verify the distro has bash
566525 ProcessBuilder testPb = new ProcessBuilder ("wsl" , "-d" , wslDistro , "-e" , "bash" , "-c" , "echo test" );
567526 testPb .redirectErrorStream (true );
568527 Process testProcess = testPb .start ();
@@ -577,7 +536,6 @@ private boolean isWsl2Available() {
577536 }
578537 }
579538
580- // No usable distro found
581539 if (defaultDistro != null && defaultDistro .toLowerCase ().startsWith ("docker-desktop" )) {
582540 log .warn ("Only Docker Desktop WSL distros found. These cannot be used to run dockerd." );
583541 log .warn ("Install a Linux distro with: wsl --install -d Ubuntu" );
@@ -596,7 +554,6 @@ private boolean isWsl2Available() {
596554 public DockerClient getClient () {
597555 if (this .dockerClient == null ) {
598556 if (usingWsl2 ) {
599- // Connect via TCP to WSL2
600557 String host = wslIpAddress != null ? wslIpAddress : "localhost" ;
601558 this .dockerClient = DockerClient .builder ()
602559 .withHost ("tcp://" + host + ":" + dockerPort )
@@ -617,7 +574,6 @@ public void stop() {
617574 if (dockerProcess != null ) {
618575 if (usingWsl2 && wslDistro != null ) {
619576 try {
620- // Kill dockerd process by port
621577 ProcessBuilder pb = new ProcessBuilder ("wsl" , "-d" , wslDistro , "-e" , "bash" , "-c" ,
622578 "sudo -n pkill -f 'dockerd.*" + dockerPort + "' 2>/dev/null ; " +
623579 "rm -rf ~/.docker-java/instances/" + instanceId );
@@ -670,7 +626,6 @@ private boolean waitForPipe() throws InterruptedException {
670626
671627 @ SuppressWarnings ("BusyWait" )
672628 private boolean waitForWslSocket () throws InterruptedException {
673- // Get WSL2's IP address
674629 wslIpAddress = runWslCommand ("hostname -I | awk '{print $1}'" , false , 5 ).trim ();
675630 if (wslIpAddress .isEmpty ()) {
676631 wslIpAddress = "localhost" ;
@@ -682,8 +637,7 @@ private boolean waitForWslSocket() throws InterruptedException {
682637 long startTime = System .currentTimeMillis ();
683638 int attempts = 0 ;
684639
685- // Try both localhost and WSL IP
686- String [] hosts = wslIpAddress .equals ("localhost" ) ?
640+ String [] hosts = wslIpAddress .equals ("localhost" ) ?
687641 new String []{"localhost" } :
688642 new String []{"localhost" , wslIpAddress };
689643
@@ -695,20 +649,17 @@ private boolean waitForWslSocket() throws InterruptedException {
695649 java .net .Socket socket = new java .net .Socket ();
696650 socket .connect (new java .net .InetSocketAddress (host , dockerPort ), 1000 );
697651 socket .close ();
698- wslIpAddress = host ; // Remember which one worked
652+ wslIpAddress = host ;
699653 log .debug ("Docker daemon is listening on {}:{} after {} attempts" , host , dockerPort , attempts );
700654
701- // Verify our dockerd is actually running
702655 String verify = runWslCommand ("ss -tlnp 2>/dev/null | grep " + dockerPort , false , 5 );
703656 log .debug ("Port {} listener info: {}" , dockerPort , verify .trim ());
704657
705658 return true ;
706659 } catch (IOException e ) {
707- // Not ready yet on this host
708660 }
709661 }
710662
711- // Log progress every 10 seconds
712663 if (attempts % 20 == 0 ) {
713664 log .debug ("Still waiting for Docker daemon... ({} seconds elapsed)" , (System .currentTimeMillis () - startTime ) / 1000 );
714665 }
0 commit comments