Skip to end of metadata
Go to start of metadata

You are viewing an old version of this content. View the current version.

Compare with Current View Version History

« Previous Version 85 Next »

Docker decided to charge a per user fee for companies and universities using their Docker Desktop for Windows package. If you are developing Windows Containers, this is the only option. If you need a way to build Linux Containers on Windows, there are several free alternatives. Since the first version of this paper was written, some of these alternatives have become trivial to install and use.

WSL Ubuntu 24.04 Distro

Originally Windows Subsystem for Linux did not support background services under systemd. When WSL 2 started to use a Linux Kernel, it was possible to build custom environments that ran systemd, but none of the alternatives offered by Microsoft in the Windows Store or installable automatically with the “wsl -install' command provided this option. Then Ubuntu releases began to offer an option where you would configure a file in the “/etc” directory and when you restarted, systemd would be running. Finally, if you install the Ubuntu-24.04 distro, it comes with systemd enabled by default.

Previously, if you typed “docker” in any Ubuntu WSL environment you would get a message telling you to install Docker Desktop for Windows. In 24.04 the package libraries have been changed to offer you the standard Ubuntu “docker.io” package. Just enter the command “sudo apt install docker.io” and you have a standard Linux docker command and background “docker engine” service running under WSL.

If Ubuntu-24.04 is the default (or only) distro, then in the Windows command line or PowerShell command line environment you can run the wsl.exe command followed by a Linux command. So simply type “wsl docker …” and then “build …” or “run …” or any other docker command.

You can create a PowerShell alias, or you can create a docker.bat file with contents

@ECHO OFF
wsl docker %*

You can also do an Ubuntu “sudo apt install docker-compose” and create a bat file for that command.

Because Windows and WSL share access to each other’s file system, and because “wsl docker” will run the Linux docker command without changing the current directory, “wsl docker” is a complete substitute for the docker.exe in Docker Desktop for Windows. The only thing missing is the GUI interface, and you can find several tools that provide it.

Of course, nothing prevents you from using Visual Studio Code with its WSL Remote option for editing and running stuff in the WSL environment from a GUI application displayed on the Windows desktop, but having “wsl docker” provides something you can use from automation scripts running in the Windows environment.

Podman Desktop for Windows

Podman is the Red Hat rewrite of the standard Docker programs. You can install it with the “winget” package manager as “winget install RedHat.Podman”. Then just replace “docker” with “podman” in every command, or rename or alias “docker” to “podman”.

Podman has been available in Windows for a few years. It is an example of one of the “custom built distros that ran systemd” that vendors built and installed after WSL 2 made that possible but before any of the standard distros supported it. Podman runs on a Fedora 40 distro that you download and generate when the first commands you run after winget install are

podman machine init
podman machine start

If you want a GUI tool that will help you do the installation and configuration and will display lists of containers, images, and volumes, you can do a

winget install redhat.podman-desktop

and then run it. If you start with this, it will discover that nothing else has been done and will lead you step by step through installing podman, creating the Fedora distro, and it will graphically display some installation options I skipped over.

Podman for Windows (History and Design)

New product released in the last year.

Ten years ago, Docker invented what we now think of as Containers as open-source software with an annual subscription for corporate production support. As a strategic decision their product almost never changes.

This created an opportunity for Red Hat to create an improved version of the Docker software called Podman. In the last year, they released a version of Podman that installs easily in Windows and, like Docker, builds and runs Containers in WSL.

There are two separate installers for two GitHub projects, but if you download Podman Desktop for Windows it will download and install both. The real work is done by the Podman software, but you have to read the instructions and follow the steps. Podman Desktop is a GUI interface that leads you through a sequence of panels to install WSL, download the Podman package, install and configure the WSL instance with recommended defaults.

Once everything is installed, you see Podman as three types of programs:

  • Fedora in WSL - Podman ships the image file and creates a Fedora 38 system with Podman pre-installed. This is also a real WSL integrated system with direct access to all Windows disks, and seamless transition between Windows and Linux commands.

  • Windows command and proxy - There is a podman.exe program which is a drop-in replacement for docker.exe. Like docker.exe, it is a client that transfers requests to the WSL environment for execution. There is also a proxy program that exposes Podmans simulation of the Docker API in Windows for use by other Windows programs that can talk to Docker (such as VS Code).

  • GUI - After it leads you through the installation, Podman Desktop Dashboard sits in the taskbar and can pop up a panel that displays lists of images, volumes, networks, and containers with buttons to delete the ones you do not need any more.

Red Hat developed Podman for Linux, where it has certain clear advantages. Unlike Docker, Podman can build images and run containers as an ordinary program, running entirely under your userid and your login session. All files are stored in your Home directory. Different developers on the same machine are ensured privacy and security. However, few people today share a multiuser Linux machine.

On a personal Windows laptop you have physical privacy. WSL is a Linux environment, but while you can create additional dummy userids, any WSL Linux environment is bound to a single Windows user who has complete control over it. Everything in WSL is hidden from other users of the same machine and (unless you work hard to bypass it) cannot be seen or used by other computers or even by other Hyper-V VMs also running on the same laptop.

Therefore, WSL Linux is effectively a single-user environment and “root” is to Linux what “Run as adminstrator” is to Windows. While most of the technical advantages of Podman in real Linux do not apply to WSL, there still is convenience to some of the additional commands and options that Podman has and Docker refuses to add. A Yale developer will see some extra useful commands and options in the following bat/powershell script that runs a container for testing:

podman secret create --replace deco.json SANDBOX.json
podman run --replace --rm -it -p 8080:8080 --secret=deco.json --name tomcat iiq:8.4-yale-1.0.52

The “docker secret create” command only works with Swarm, but Podman makes it a normal command. Then they add --secret to the run command so you don’t have to use compose. They add --replace to both commands to delete and replace an existing secret or container with the same name before you create a new one.

Podman supports Dockerfiles, although they allow you to use the alternate name “Containerfile” to avoid brand name confusion.

A Container is just a type of Linux Process

A Linux program “running in a container” is just a program that has been started in a special way. It doesn’t look or behave differently from normal programs.

$ docker run --name tomcat -d --rm tomcat:jdk11-tc9
02f58c9115a4b0e35a22ec496cec9d5808e1e4394558a0491650d6e18d179a25
$ ps -ef|grep tomcat
root       11797   11777 11 12:55 ?        00:00:01 /opt/java/openjdk/bin/java ... start

In this example we start a container that runs Tomcat, then display the running tomcat program as an ordinary Linux process. A Tomcat process looks pretty much the same whether it was started as a container, or as a background service under systemd, or interactively by a logged in user running the /bin/startup script.

The system services that create containers have always been part of Unix, but for decades they were obscure technical features. Then Docker released a software package to configure, build, and run application programs in a special configuration that became widely known as “Containers”. However, the Linux environment Docker decided to use to run applications is only one specific configuration of the more general idea of containerization.

A Docker application container appears to be a Linux system running in “single-user” mode. Nothing is running in the system except a single user login with its shell. You can boot any Linux system into single user mode, which is used to do maintenance on the system, but all images in Docker Hub create this appearance.

The original Unix design placed all system information in the structure you normally think of as “/”. While it contains directories and files, it also contains real disks, network disks, network adapters, sockets, the process created when a program runs, shared libraries loaded into memory, and so on.

When one program starts any other program, it can edit this “namespace” structure to remove objects it doesn’t want the new program to access and add synthetic objects. For example, it can remove all real LAN adapters but create an imaginary LAN backed by VPN software, so the new program can only access the VPN and not the real local network.

Containers are built on this capability. Docker invented a particular configuration and file format to create both a specific image of directories and files and a set of VPN dummy networks. This file format was then standardized by an industry working group.

Extra Responsibilities

At Yale, when an application runs on a VM it is the Linux Systems Group that installs and configures Java and Tomcat and installs prerequisite software. The developers only install the WAR into the Tomcat “webapps” subdirectory.

When developers build a container to run the application, they start with a base container from Docker Hub that typically contains a version of Java and Tomcat. They may provide the Yale root CA certificates in a cacerts file and the Yale Kerberos configuration in a krb5.conf file. The Dockerfile may run the package manager to install prerequisite shared libraries.

All containers can then run on a generic minimal Linux system. There is no need to customize a VM for any application. However, developers now need to know the system configuration files and packages that were previously the responsibility of the Systems group.

Alternatives

Docker tries to use a single implementation to work on a single user laptop, a large multiuser corporate server, a cluster of enterprise servers, and a Cloud based SaaS. Larger companies with more resources (Microsoft, Amazon, Red Hat/IBM) provided their own high-end alternatives for production use, while Podman is interesting for the developer’s laptop, which is the focus environment for this article.

At Yale, a developer cannot create production container images. The developer edits a Dockerfile project that generates images. A local image can be built and unit tested on the laptop. The Dockerfile project is then checked into git.yale.edu and a Jenkins job builds the production image and stores it on the Harbor image repository server.

Therefore, the rest of Yale does not know and cannot care what software is used on the laptop to test the Dockerfile project. It has to be compatible with the Dockerfile standard, but it can be Podman or any other compatible tool.

WSL is a different Container Runtime

Windows Subsystem for Linux is a tightly coupled Linux environment that can run Linux commands as if they were a natural part of Windows. Someone familiar with a Linux command like “grep” can use it on the Windows command line by prefixing the command name with “wsl”:

C:\Windows\System32>tasklist|wsl grep sql
sqlwriter.exe                 5804 Services                   0      8,504 K
sqlceip.exe                   7780 Services                   0     53,584 K
sqlservr.exe                  7848 Services                   0    199,708 K

Under the covers, cmd.exe is piping the standard output of tasklist.exe to standard input of wsl.exe. The wsl.exe program communicates to the WSL Linux VM passing the current Windows environment (including the current directory), the grep command, and the stream of characters from the pipe. This sets up the environment, runs the Linux program (/bin/grep), and passes it the stream of characters as standard input. The program runs in a Linux system where all the Windows local disks have been mounted with file access permissions on the Windows files inherited from the user who executed the Windows command.

WSL is implemented as a single Linux VM running the Microsoft Linux kernel, on top of which a Microsoft container runtime supports multiple Linux “distribution” containers. You can install these WSL containers using Windows Store or wsl.exe. A subset of the available containers is:

>wsl -l --online
The following is a list of valid distributions that can be installed.
Install using 'wsl.exe --install <Distro>'.
NAME                                   FRIENDLY NAME
Ubuntu                                 Ubuntu
Debian                                 Debian GNU/Linux
kali-linux                             Kali Linux Rolling
Ubuntu-22.04                           Ubuntu 22.04 LTS
OracleLinux_9_1                        Oracle Linux 9.1
openSUSE-Leap-15.5                     openSUSE Leap 15.5

Microsoft tries to keep things simple, so they use the term “distro” to mean two different things that they assume the reader will not have to distinguish. In a list of install options, “OracleLinux_9_1” refers to an image tar file prepared by Oracle and submitted to Microsoft for distribution. When you do a “wsl --install”, this image file is used to create a WSL container, and the container is also given the name “OracleLinux_9_1”. After that, when the wsl documentation refers to running a program or an interactive shell in a “distro” they are talking about the container and not the image used to build it.

Over time the vendor may provide an updated image file under the same name. The “Ubuntu-22.04” name has been used for two years, but if you install it today and then display the /etc/lsb-release file you find it has “DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS”, so this is probably the fourth refresh update of the image.

A WSL container is different from a Docker container. Docker containers are intended to be short term and lose all their disk data when the system shuts down. WSL containers have a Microsoft virtual hard drive file (a VHDX, like a VM disk), so changes are permanent until you discard the container.

WSL containers shut down and restart. Traditionally they run in single-user mode, but Microsoft changed the rules and today many of them, notably Ubuntu, will run in “systemd” mode if you add “[boot] systemd=true” to the /etc/wsl.conf file in the image file system disk and restart the container.

“wsl docker”

When WSL Ubuntu runs in the default single-user mode, it cannot install and run a Docker Engine. However, if you enable systemd, then Ubuntu will let you “sudo apt install docker.io” or even install docker as a snap. This puts a docker command in WSL Linux, and like any other command you can run from Windows by adding the wsl.exe prefix.

Since the primary topic of this paper is building Docker images, the specific Windows command of interest is

wsl docker build .

You execute this command from a Windows directory containing a Dockefile project. Windows invokes the Linux docker command in the same current directory, but in the Linux system. There it transfers data to the Docker Engine, which executes the operations in the Dockerfile, builds the image and stores it in the Engine’s local image repository.

If you don’t like typing “wsl docker”, you can create a docker.bat file in your path with the line:

wsl docker %*

This is the simplest way to install and run Docker on Windows without using any licensed material. You get Ubuntu from the Windows Store. You get docker from the Ubuntu package libraries. There is no docker.exe, but there is no need for one.

You could also install Podman in WSL Ubuntu, but you get a little more function (and a Fedora WSL container) if you install Podman into Windows instead.

Special Purpose WSL “distro” Images

The “wsl --import” command will create a WSL container from any correctly formatted tar file on your disk. Software packages can include such a tar file and use it to create WSL containers directly, bypassing the Microsoft distribution system.

Docker Desktop for Windows includes a tar file for their Moby Linux and creates a WSL container named “docker-desktop-data”.

Podman includes a Fedora 38 tar file and creates a container named “podman-machine-default”. You can then create additional WSL Fedora containers with different names using the “podman machine init” command.

Plumbing

This is a very complicated topic that must be explained precisely if you are to understand how things work. Otherwise, it will just appear as if testing containers works by magic.

When the WSL feature is activated, it creates a VM to run the Microsoft Kernel and the containers. That VM has a virtual LAN adapter, connected to a virtual network called “WSL”. A matching virtual LAN adapter is created in Windows connected to the same virtual network.

Windows sees its adapter as:

Ethernet adapter vEthernet (WSL):
   Connection-specific DNS Suffix  . :
   IPv4 Address. . . . . . . . . . . : 172.21.16.1
   Subnet Mask . . . . . . . . . . . : 255.255.240.0
   Default Gateway . . . . . . . . . : 

While all WSL Linux containers share a common eth0 adapter with its randomly chosen IP address:

2: eth0: 
    inet 172.21.21.137/20 brd 172.21.31.255 scope global eth0

The eth0 adapter is configured to treat the Windows 172.21.16.1 address as the gateway. Therefore, any client connect from the WSL Linux containers to the Yale network or the Internet flows thorugh the Windows system and uses the networks to which the Windows host is connected at the time.

However, the connections work only one way. The Windows host system can connect to the WSL VM using the 172.21.21.137 address, but this is not visible to any computer outside of the Windows host. Therefore, WSL applications and containers can only be tested from the Windows host (and other WSL client programs).

The WSL containers all share the one VM LAN adapter. Port numbers on that adapter are owned by the first program in any WSL container to bind to them. A program running in WSL Ubuntu can get back a “port is being used by another program” error because a program in WSL Fedora is already using it.

Every system also has a Loopback virtual adapter, also known by the name “localhost” and the IP address 127.0.0.1. The loopback is entirely synthetic and in every other case is local to the one system. Loopback is handled by a device driver in the kernel. Here the Linux Kernel is shared by the WSL containers, so port numbers are owned by the first program in any WSL container to bind to it.

However, to allow Windows to easily communicate to WSL programs, the loopback driver in the Linux Kernel communicates to the loopback driver in the Windows kernel to extend the impression of a single shared device. A Windows Browser can communicate to a WSL Linux Web server by using a URL like “http://localhost:8080”.

Of course, using Internet protocol it is also possible to connect from Windows to WSL Web servers using the URL “http://172.21.21.137:8080”, but you have to discover this IP address and there is no nice hostname you can use instead of the address.

Suppose you run Tomcat as an ordinary Linux program (not in a container) in any WSL distro. It will bind to 0.0.0.0:8080, which means port 8080 on any LAN adapter it can see. WSL containers see two LAN adapters, so it will bind to loopback (127.0.0.1:8080) and eth0 (172.21.21.137:8080). A Widows Browser talks to Tomcat through a virtual network using the eth0 IP address, and through the internal Kernel to Kernel connection between the two loopback device drivers using “localhost”.

Now run Tomcat in a Docker or Podman container using the -p 8080:8080 parameter on the run command. Inside the container, Tomcat binds to 0.0.0.0:8080, but the container has a restricted namespace where the only network it sees is a VPN generated by Docker/Podman. A VPN is necessary because, unlike WSL containers, Docker containers are completely independent of each other. In theory, dozens of Docker containers can all bind to port 8080 on what they see as their LAN adapter and never get a “port is being used by another program” error. The containers use the VPN to talk first to the container Engine.

The Docker Engine in the WSL container named “docker-desktop-data” or Podman in the WSL container named “podman-machine-default” bind to 0.0.0.0:8080 and like Tomcat in the previous example, this becomes a bind to loopback (127.0.0.1:8080) and eth0 (172.21.21.137:8080).

A Windows Web Browser can connect to Tomcat in the container with the same URLs. The only difference is that the Browser communicates to the Container Engine, which forwards the data to the Docker container using the VPN.

The connection between the Windows Browser and any program running in WSL is a standard feature of WSL itself and did not depend on any additional code added by Docker or Podman, nor was it helped along by the docker.exe or podman.exe program. This means that if you decide to use the “wsl docker” or “wsl podman” approach and install nothing in Windows itself, you can still run containers with “wsl docker run -p …” or “wsl podman run -p …” and test then with a Windows Browser and a “http://localhost” URL.

Exposing the Docker API to Windows Programs

The Docker Engine exposes an API through a Unix socket to other programs running on the same Linux system. Podman exposes an identical API to allow third party Docker tools to work with it.

However, Unix sockets do not exist on Windows. Docker created a proxy program that runs in Windows and exposes the API using a Windows “named pipe”, and again Podman created its own proxy that works the same way. There are not a lot of programs that know how to talk to the named pipe, but the Docker plugin for VS Code is at least one example.

If you run Docker on a real Hyper-V Linux VM instead of WSL, you can configure the Docker Engine to listen for requests on port 2375 of a virtual LAN adapter. However, Docker does not provide authentication or access control, so the virtual network has to be entirely private inside your laptop.

This is one case where the Docker approach is so bad that Podman refused to duplicate it. You can use Podman on one machine to connect to Podman acting as an Engine on another machine, but it uses SSH. Security is provided by the SSH login, normally using public keys. Once the client Podman has done an SSH login, it can run a podman command as that user on the other system, which provides access to the images and files in that user’s home directory.

Configure the Docker Engine in a Hyper-V Linux VM

Create a Linux VM with Hyper-V Quick Create, Ubuntu Multipass, or download the ISO and create the VM from scratch. When it comes up, you can use sudo apt or sudo yum to install the normal docker.io or podman packages.

This is not the place to discuss Hyper-V networking in detail. There is a Default network that connects VMs to the host Windows. You can also create other networks, including ones connected to a real LAN adapter. You can always configure your VM to connect to more than one network, but then you have to configure a static IP address because it will be a server of sorts. Assume the address on the network you want to use is 192.168.2.204.

On the VM, edit the file /lib/systemd/system/docker.service. Find the line beginning “ExecStart=/usr/bin/dockerd”, and add a “-H” parameter followed by that IP address:

ExecStart=/usr/bin/dockerd -H fd:// -H 192.168.2.204 --containerd=/run/containerd/containerd.sock

save the file and then restart the docker.service or reboot the VM.

DOCKER_HOST

All versions of docker.exe (but specially the open-source one called “docker-cli” on GitHub and Chocolatey) will look for an Environment variable named DOCKER_HOST that points to a network address of a Linux machine running Docker (or Podman). To connect to the Engine configured above, the value of DOCKER_HOST should be ”tcp://192.168.2.204:2375”.

You can install both podman.exe and docker.exe on the same Windows system. However, you do not want DOCKER_HOST to be set when the podman.exe command or Podman Desktop Dashboard is running. In that case, it is better to set the environment variable locally where the docker.exe program is running. A solution may be a docker.bat file that looks like this:

@ECHO OFF
set DOCKER_HOST=tcp://192.168.2.204:2375
set PATH=%PATH%;c:\dockercli\bin
c:\dockercli\bin\docker.exe %*
  • No labels