Since I wrote this originally, Docker on Windows has become a first-class supported tool, with a Windows Installer and well-documented installation processes from docker and from Microsoft.
Today, I actually install docker using boxstarter scripts where I can Enable-WindowsOptionalFeature -Online -FeatureName containers -All
and then choco upgrade -y docker-desktop
as well as installing tooling for VS Code code --install-extension "ms-azuretools.vscode-docker"
.
I've left the rest of these notes here as a historical record, more than anything else. You should not expect the script below to work, but you certainly don't need it, since you can install via the installer or automate with docker desktop on chocolatey or the PackageManagement Docker Provider, and the PowerShell team now publishes official PowerShell Docker images for Debian and Ubuntu, CentOS and Fedora, OpenSuse, and Alpine, as well as Windows NanoServer and Server Core over on docker hub.
There are a lot of good reasons to use Docker. Right now, my reason is that I need to work with PowerShell on Linux, and with Windows 10 anniversary update, Windows containers now support Nano Server as well (which is the other logical place to test the new open source PowerShell).
Currently, Docker supports running Linux images or Windows images in their container service, but not both in the same server, so to get both, we need to first install Docker using the installer, (which handles dependencies like requiring Hyper-V and Containers) and then install the most recent version of the Windows service separately, and configure them to run together.
Here's a full explanation, but you could just run the whole script and then skip to installing nanoserver
NOTE: All of these commands should be run from an elevated host.
$DockerInstaller = Join-Path $Env:Temp InstallDocker.msi
## Personally, I like BITS:
# Start-BitsTransfer https://download.docker.com/win/stable/InstallDocker.msi -Destination $DockerInstaller
Invoke-WebRequest https://download.docker.com/win/stable/InstallDocker.msi -OutFile $DockerInstaller
## Now install it
msiexec -i $DockerInstaller -quiet
To support Windows images (including Nano Server) in docker, we need the separate service, which currently means we need the very latest release of the docker service for windows containers. Note that this command writes directly to Program Files, since we're elevated.
Invoke-WebRequest "https://master.dockerproject.org/windows/amd64/dockerd.exe" -OutFile "${Env:ProgramFiles}\docker\dockerd.exe"
We need to manually register the service for that one, and to specify an alternate pipe address to listen on. I also like to customize the service name (by default it's just "docker") so that it matches the one set by the Linux engine.
& "${Env:ProgramFiles}\docker\dockerd.exe" -H npipe:////./pipe/win_engine --service-name=com.docker.windows --register-service
If you don't want the Linux container host, then you can follow the docs for containers, but since we've already taken care of dependencies by running the Docker installer, we just need to fix one known issue with Windows Containers, which requires disabling Oplocks:
# Ensure the feature is enabled (this was taken care of by the Docker Installer, but for completeness sake) ...
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V, Containers -All
If you've played with Windows docker previously, and disabled Oplocks, you should enable them again now. If you didn't, there's no need to worry about this.
Set-ItemProperty -Path 'HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers' -Name VSmbDisableOplocks -Type DWord -Value 0 -Force
In fact, to make sure I can tell them apart, I like to customize the display text so I can remember which is which:
Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\com.docker.windows DisplayName "Docker Engine for Windows"
Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\com.docker.windows Description "Windows Containers Server for Docker"
Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\com.docker.service DisplayName "Docker Engine for Linux"
Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\com.docker.service Description "Linux Containers Server For Docker"
For PowerShell support, there is a Docker module, so we can avoid parsing the output of docker ps -a
every time we need to know the name or id of a running image. However, it's also still in pre-release, so you have to install it from it's appveyor feed:
Register-PSRepository -Name DockerPS-Dev -SourceLocation https://ci.appveyor.com/nuget/docker-powershell-dev
Install-Module Docker -Repository DockerPS-Dev -Force
You should not even need to reboot, just start the windows service(s).
Start-Service com.docker* -Passthru
We actually have two Docker services (aka daemons) running, so we can run Linux or Windows docker containers. However, whenever we want to use the second one, we have to provide the host address.
Eventually, Microsoft and Docker will get this worked out so they can all play together and we won't need two services on Windows, but in the meantime, it's not that big of a deal, because we can just put the parameter in a hashtable and splat it each time.
For instance, here's how to get a simple image for each:
$dw = @{ HostAddress = 'npipe://./pipe/win_engine' }
Request-ContainerImage @dw microsoft/nanoserver
Request-ContainerImage ubuntu
And to set up nano and remote into it, using the same $dw
hashtable:
$w = New-Container @dw microsoft/nanoserver | Start-Container @dw -Passthru
# interestingly, New-PSSession didn't need me to tell it about the host:
$session = New-PSSession -Name Nano -ContainerId $w.ID
# Now you can enter and `exit` at will, or use Copy-Item -ToSession ...
Enter-PSSession $session
If you forget to specify the HostAddress, you'll just get errors because the container or image isn't available on the other service.
Since we've gone ahead and installed an ubuntu image, and we're die-hard PowerShellers, we really need that Enter-PSSession
functionality for ubuntu, right?
$u = New-Container ubuntu -Input -Terminal -name psu | Start-Container -Passthru
Start-ContainerProcess $u apt-get update
Start-ContainerProcess $u apt-get install libicu55 libunwind8
# download here and push in, just to show how:
Invoke-WebRequest https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.9/powershell_6.0.0-alpha.9-1ubuntu1.16.04.1_amd64.deb -OutFile ~/Downloads/powershell_6.0.0-alpha.9-1ubuntu1.16.04.1_amd64.deb
Copy-ContainerFile $u ~/Downloads/powershell_6.0.0-alpha.9-1ubuntu1.16.04.1_amd64.deb -Destination /home -ToContainer
# Install it using dpkg -i
Start-ContainerProcess $u dpkg '-i' /home/powershell_6.0.0-alpha.9-1ubuntu1.16.04.1_amd64.deb
And that's it. You can now run PowerShell on your Ubuntu host. I've had to do so using the actual docker exec
command rather than Start-ContainerProcess
, because
docker exec -it psu powershell
docker attach psu
Noticed
Register-PSRepository -Name DockerPS-Dev -SourceLocation https://ci.appveyor.com/nuget/docker-powershell-dev
fails with a 404 error, was the distribution repo discontinued just like the source one was archived on github?