a pleasant descent into madness

Tag: WSL2

WSL2 Docker not talking to Windows Host

Or: “WSL2 Docker is not connecting to my Host!!!! HELP!”

In another post i already talked about a setup to get both Windows Host / Windows Host Docker and WSL Docker talking to each other.
The setup broke at some point and i figured out a pretty automated solution for this… for now, let’s hope it works after the next WSL or Docker update.

My solution was following Setup:

1- Start Docker in WSL automatically on BOOT

2- Wait a minute after Logon and start Docker Desktop (this post)

Prerequisites:
– Docker Desktop WSL Integration is turned off.
Docker on WSL has been detached from Docker Desktop for the Windows Host
– Docker Desktop Autostart is disabled

Now, assuming you followed part 1.
Let’s continue and create a file and paste this powershell script provided by StackOverflow User StingyJack (Thanks m8!).

In my case, the file is running in “C:/Sources/Scripts_Autostart” and is called “docker-restart.ps1”.

Write-Output "$((Get-Date).ToString("HH:mm:ss")) - Restarting docker"

foreach($svc in (Get-Service | Where-Object {$_.name -ilike "*docker*" -and $_.Status -ieq "Running"}))
{
    $svc | Stop-Service -ErrorAction Continue -Confirm:$false -Force
    $svc.WaitForStatus('Stopped','00:00:20')
}

Get-Process | Where-Object {$_.Name -ilike "*docker*"} | Stop-Process -ErrorAction Continue -Confirm:$false -Force

foreach($svc in (Get-Service | Where-Object {$_.name -ilike "*docker*" -and $_.Status -ieq "Stopped"} ))
{
    $svc | Start-Service 
    $svc.WaitForStatus('Running','00:00:20')
}

Write-Output "$((Get-Date).ToString("HH:mm:ss")) - Starting Docker Desktop"
& "C:\Program Files\Docker\Docker\Docker Desktop.exe"
$startTimeout = [DateTime]::Now.AddSeconds(90)
$timeoutHit = $true
while ((Get-Date) -le $startTimeout)
{

    Start-Sleep -Seconds 10
    $ErrorActionPreference = 'Continue'
    try
    {
        $info = (docker info)
        Write-Verbose "$((Get-Date).ToString("HH:mm:ss")) - `tDocker info executed. Is Error?: $($info -ilike "*error*"). Result was: $info"

        if ($info -ilike "*error*")
        {
            Write-Verbose "$((Get-Date).ToString("HH:mm:ss")) - `tDocker info had an error. throwing..."
            throw "Error running info command $info"
        }
        $timeoutHit = $false
        break
    }
    catch 
    {

        if (($_ -ilike "*error during connect*") -or ($_ -ilike "*errors pretty printing info*")  -or ($_ -ilike "*Error running info command*"))
        {
            Write-Output "$((Get-Date).ToString("HH:mm:ss")) -`t Docker Desktop startup not yet completed, waiting and checking again"
        }
        else
        {
            Write-Output "Unexpected Error: `n $_"
            return
        }
    }
    $ErrorActionPreference = 'Stop'
}
if ($timeoutHit -eq $true)
{
    throw "Timeout hit waiting for docker to startup"
}

Write-Output "$((Get-Date).ToString("HH:mm:ss")) - Docker restarted"

Next up: Open Task Scheduler and create a new Basic Task

Name it like you want, i chose “Start Docker Desktop after WSL Docker”.
Next, then select “When i log on”

Next: set “Program/Script” to Powershell
Then set argument to

-windowstyle hidden -ExecutionPolicy Bypass -file C:\Sources\Scripts_Autorun\docker-restart.ps1

Of course you will choose the folder where you put the script.
don’t forget to add quotations if your path contains spaces!

Click next and after finishing the setup open the Task back up again. (Edit properties)

In the General Tab set “Run only when user is logged on” and “Run on highest privilegues”

Open the Trigger in the “Triggers” tab and set a Delay for 1 minute.

That’s it!
Have, of course you will have to chill about a minute or two after rebooting your pc.
If you’re faster than your scripts, this won’t work.
You might also want to adjust the delay time for the script for part 1. and 2.

Get Docker running in WSL 2 independently of Docker Desktop

Or: “Get Docker running in WSL without WSL Integration”

Recently, i’ve had a special case where i had to develop a project with Docker containers running on the Windows Host Docker, other containers running in WSL and some of the tools running directly on the Windows Host.
(Basically, a big confusing mess).

To get this 3-way-communication going, i actually had to install Docker in WSL independantly from Docker Desktop on my Host.
This turned out to be a little tricky, just because i didn’t really know where to look up the sources.

So if anyone of you have these special needs, feel free to follow me along.
Take these steps:

  • Install Docker Desktop normally on your host, do not enable WSL Integration but install all tools necessary for WSL Integration (just in case)

  • Open up your WSL and execute following commands:
sudo apt-get update
 sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

Get the Docker GPG Key

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Set up stable repository:

 echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Do another update

sudo apt-get update

Install docker engine independantly of host:

 sudo apt-get install docker-ce docker-ce-cli containerd.io

After this, i restart my WSL instance (Close and open up again) and run

sudo service docker start

And check if docker is running correctly:

sudo docker run hello-world

This is basically copied and condensed from

https://docs.docker.com/engine/install/ubuntu/ [12th of April 2021]

As i don’t know if the site content ever changes i wanted to have a copy of the steps myself.

Now you have docker only in wsl, you will have to start up the service manually if you open up your WSL instance for the first time or if WSL is stopped completely.
But i’ll offer a fix for that later.

This will also work without Docker installed on the host, so both instances don’t know about each other and Docker Desktop cannot f* with your network too much.

[DEPRECATED] Automatically start docker service in WSL2

DEPRECATED, look at Start WSL 2 Service on Host startup instead.

I keep forgetting to start the docker service when opening up an instance of WSL(2)

Let’s fix that!

So what do we want to do?
We want to run the docker service from startup!
And that has to be done on sudo… but we don’t want to be reminded of that constantly
(in the form of a prompt to enter the sudo password on every WSL2 instance start).

First of all, sadly, we need to stop WSL from asking us for the sudo password.

Stop WSL2 from asking for sudo password

Next up, open up wsl and run

sudo service docker start > /dev/null 2>&1 &

This starts the service in the background while piping the output to null

As you opened a new WSL instance, this should be happening within your userprofile and opening up the .bashrc file.

Add following line to the end of the file:

sudo service docker start

Now WSL instance startup may take some seconds depending on how long it will take to start up docker in WSL.
Have fun!

Talking from (Docker) WSL to (Docker on) the Windows Host

(Also works without Docker)

While working on a project employing a Client in WSL 2 (Windows Subsystem for Linux) and a Web-Api running on the Windows Host i noticed that there are several complex issues to resolve to get the communication going.
The main issue was that the connection between these two instances was hard to establish and needed lots of maintenance after each reboot or network reconnect of the machine which the ecosystem was running on.

My Setup:

I’m running a Javascript client framework on WSL2 Docker and a ASP .Net 5 REST Server directly on the Windows (during dev), but the Server might be moved to a docker container on a windows host later.

It’s hard to set up a connection between WSL2 Docker and Windows Host/Windows Host Docker, a solution is to employ a Reverse proxy on the Client in WSL2 Docker and prompt the Windows Host Server to listen to the WSL Adapter IP Address.

Figure: Talking from WSL 2 to Windows Host or Windows Host Container using Rev. Proxy

TALK FROM LINUX

(SUBSYSTEM FOR WINDOWS)

The first task is to set up the WSL Adapter IP Address for your reverse proxy client-side in wsl.

Run a WSL2 Terminal, from your current (home/default) directory, run
sudo nano .bashrc

Scroll completely down and add these lines:

#Manually added for setting ENV Variable WSL_WINDOWS_HOST
export WSL_WINDOWS_HOST=cat /etc/resolv.conf | grep nameserver | cut -d ' ' -f 2

Now, when you run your docker container in WSL, pass the environment variable WSL_WINDOWS_HOST

for example using this parameter (or a docker compose):
-e APIHOST=$WSL_WINDOWS_HOST

Inside the container, you just have to configure the proxy to use

http://$APIHOST:[YOURPORT] (or https of course)

Well, that’s done, now the WSL Container wants to talk to Windows, but nobody is listening, story of my life, let’s fix that up.

LISTEN FROM WINDOWS

The second task to get this running was to notify the Application about the WSL Adapter IP Address.
If you want to see the WSL Adapter IP Address just type “ipconfig” in powershell and find the WSL Adapter IPv4 Address.

You would want to inform your Server Application on the WSL Adapter IP Address, it needs to listen to this to receive connections from WSL.

To employ this IPv4 Address (make the Server listen to it) you want to change your launchSettings.json to listen to “http://[YOUR WSL ADAPTER IP ADDRESS]:[YOUR PORT]”


BUT:
The WSL Adapter IP Address changes on every Windows Host reconnect to any network and consequently on every reboot and/or occasionally on wake-ups of the Host system (from Sleep or Hibernate).
Normally, the IP-Address to listen to has to be configured in your (server) application, that means that you have to set it before the server application starts.
If the IP constantly changes, it can be time consuming to look up the WSL Adapter IP Address every time and change the server settings accordingly.

Well that’s where Task Scheduler and Powershell play together, you will want to create a powershell script that reads the WSL Adapter IP Address and stores it in an environment variable, this environment variable can then be used by your server application.

Luckily, i took the time to figure out the Powershell and corresponding Task Scheduler settings:

This is the powershell script, here called “wsl_adapter_ip_to_env.ps1” and located in “C:/Sources/Scripts_Autorun” on the Windows host:

$ip = (Get-NetAdapter -Name WSL | Get-NetIPAddress).IPv4Address
setx WSL_ADAPTER_IP $ip

Create this file and open up the Task scheduler (Windows Key + R, then “taskschd.msc”)

Create a folder in the Task Scheduler Library (for better Script management), i created
“Task Scheduler Library/WSL_DEV”

Create a new Task (NOT a Basic Task) with following Settings:
GENERAL TAB:
> Description: “Create ENV Variable WSL_ADAPTER_IP with WSL ADAPTER IP Address”
> Security Options: Run only when user is logged in (your user)
> Security Options: Run with highest privilegues

TRIGGERS TAB:
> NEW TRIGGER: At logon (your user), Delay: 1 minute, enabled
(this will update the WSL Adapter IP variable on logon)
> NEW TRIGGER: On an event (basic), Log: Microsoft-Windows-NetWorkProfile/Operational, Source: NetworkProfile, Event ID: 10000, Enabled
(this will update the WSL Adapter IP on every Windows Host Network reconnect, no matter which adapter or network type)

ACTIONS TAB:
> NEW ACTION, START A PROGRAM
Program/Script: powershell
Add Arguments: -windowstyle hidden -ExecutionPolicy Bypass -file C:\Sources\Scripts_Autorun\wsl_adapter_ip_to_env.ps1

That’s it, now you will have an updated WSL Adapter IP Address Environment variable every time it changes.

There are multiple approaches to use this environment variable in a .Net API, currently i’m doing this in “Program.cs”

      public static IHostBuilder CreateHostBuilder(string[] args)
{
string wslAdapterIp = Environment.GetEnvironmentVariable("WSL_ADAPTER_IP");

return  Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
                if (!String.IsNullOrEmpty(wslAdapterIp))
                {
                    Console.WriteLine("WSL_ADAPTER_IP Environment variable is set, using.");
                    webBuilder.UseUrls($"http://{wslAdapterIp}:5000","https://{wslAdapterIp}:5001");
                }
            });
    }

Note that you will need to pass this environment variable into the container via the “-e” flag or a docker-compose file, this example will work directly on the host using the env var that has been set by the task scheduler.

Well then, happy coding, leave a like, comment or whatever, until next time and don’t forget to bash in the bell icon and call the foo fighters.
Bye!


(As usual, i’m open for constructive criticism, fixes and anything else that you might want to contribute)

Important Finding!

If WSL-Integration is active in Docker Desktop, none of this will work.
Disable WSL-Integration under Docker-Desktop Settings -> General

This also implies that you install docker on your WSL (2) the same way as you would on a seperate machine.


Another important finding!

This setup worked for me for a few weeks, then suddenly stopped working.

I put some hours of figuring-out into it and noticed another problem:

It seems that the network settings are scrambled/broken when Docker Desktop starts before WSL2 – Docker.

That means that if Docker Desktop is started on Windows Startup as usual, WSL Docker will be unable to talk to the Windows Host.

To fix this very strange behavior (please somebody take the time to explain this to me?) i have 2 workarounds:

  1. Disable Docker Desktop autostart and run only after WSL 2 Docker has been started.
  2. A sophisticated solution using Task-Scheduler, good timing and powershell scripts.


IMPORTANT FOOTNOTES

Please note that the bashrc script on WSL will only run if WSL is starting, this means that if the WSL Adapter IP Changes during runtime, you need to restart WSL by closing all Windows and opening a new one or by rebooting the machine if you prefer.

Please note that Windows works in strange ways, for example, if you open a command line window or start a process, on start, the process will load all environment variables and not update them (ever, again), this means that if you are running Visual Studio or a Command Line and your Adapter IP Environment variable changes, you might want to restart Visual Studio or the Command window.

These behaviors cannot be influenced by me and are operating system dependant quirks, i’m already looking into some kind of force-reload mechanisms, but other than working with temporary files (which is NOT a good way) i could not yet come up with a solution for VS or WSL (though i found a force-reload for cmd/ps).

© 2022 Yavuz-Support Blog

Theme by Anders NorenUp ↑