├── .gitignore ├── images ├── packer_vagrant_docker.png └── windows_docker_machine.png ├── appveyor.yml ├── LICENSE ├── Vagrantfile ├── scripts └── create-machine.ps1 └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ 2 | scripts/fiddle.ps1 3 | -------------------------------------------------------------------------------- /images/packer_vagrant_docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefanScherer/windows-docker-machine/HEAD/images/packer_vagrant_docker.png -------------------------------------------------------------------------------- /images/windows_docker_machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StefanScherer/windows-docker-machine/HEAD/images/windows_docker_machine.png -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | image: Visual Studio 2017 3 | 4 | install: 5 | - curl.exe -Lo "C:\Program Files\docker\dockerd.exe" https://master.dockerproject.com/windows/x86_64/dockerd.exe 6 | - set PATH=c:\program files\docker;%PATH% 7 | - sc stop com.docker.service 8 | # - choco install -y vagrant 9 | 10 | build: off 11 | 12 | test_script: 13 | # - vagrant validate 14 | - ps: .\scripts\create-machine.ps1 -machineHome C:/Users/foo -machineName 2016 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2022 Stefan Scherer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.require_version ">= 1.8.4" 5 | 6 | Vagrant.configure("2") do |config| 7 | config.vm.communicator = "winrm" 8 | 9 | config.vm.synced_folder ".", "/vagrant", disabled: true 10 | home = ENV['HOME'].gsub('\\', '/') 11 | config.vm.synced_folder home, home 12 | 13 | config.vm.define "2016", autostart: false do |cfg| 14 | cfg.vm.box = "windows_2016_docker" 15 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 2016" 16 | end 17 | 18 | config.vm.define "2016-box", autostart: false do |cfg| 19 | cfg.vm.box = "StefanScherer/windows_2016_docker" 20 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 2016-box" 21 | cfg.vm.provider "virtualbox" do |v, override| 22 | override.vm.network :private_network, ip: "192.168.59.50", gateway: "192.168.56.1" 23 | end 24 | end 25 | 26 | config.vm.define "1709", autostart: false do |cfg| 27 | cfg.vm.box = "windows_server_1709_docker" 28 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 1709" 29 | end 30 | 31 | config.vm.define "1803", autostart: false do |cfg| 32 | cfg.vm.box = "windows_server_1803_docker" 33 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 1803" 34 | end 35 | 36 | config.vm.define "1809", autostart: false do |cfg| 37 | cfg.vm.box = "windows_server_1809_docker" 38 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 1809" 39 | end 40 | 41 | config.vm.define "1903", autostart: false do |cfg| 42 | cfg.vm.box = "windows_server_1903_docker" 43 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 1903" 44 | end 45 | 46 | config.vm.define "2019", autostart: false do |cfg| 47 | cfg.vm.box = "windows_2019_docker" 48 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 2019" 49 | end 50 | 51 | config.vm.define "2019-box", autostart: false do |cfg| 52 | cfg.vm.box = "StefanScherer/windows_2019_docker" 53 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 2019-box" 54 | cfg.vm.provider "virtualbox" do |v, override| 55 | override.vm.network :private_network, ip: "192.168.59.51", gateway: "192.168.56.1" 56 | end 57 | end 58 | 59 | config.vm.define "2022", autostart: false do |cfg| 60 | cfg.vm.box = "windows_2022_docker" 61 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 2022" 62 | end 63 | 64 | config.vm.define "2022-box", autostart: false do |cfg| 65 | cfg.vm.box = "StefanScherer/windows_2022_docker" 66 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 2022-box" 67 | cfg.vm.provider "virtualbox" do |v, override| 68 | override.vm.network :private_network, ip: "192.168.59.52", gateway: "192.168.56.1" 69 | end 70 | end 71 | 72 | config.vm.define "2025", autostart: false do |cfg| 73 | cfg.vm.box = "windows_2025_docker" 74 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName 2025" 75 | end 76 | 77 | config.vm.define "insider", autostart: false do |cfg| 78 | cfg.vm.box = "windows_server_insider_docker" 79 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName insider" 80 | end 81 | 82 | config.vm.define "lcow", autostart: false do |cfg| 83 | cfg.vm.box = "windows_server_1903_docker" 84 | cfg.vm.provision "shell", path: "scripts/create-machine.ps1", args: "-machineHome #{home} -machineName lcow -enableLCOW" 85 | ["vmware_fusion", "vmware_workstation"].each do |provider| 86 | config.vm.provider provider do |v, override| 87 | v.memory = 5120 88 | end 89 | end 90 | end 91 | 92 | ["vmware_fusion", "vmware_workstation"].each do |provider| 93 | config.vm.provider provider do |v, override| 94 | v.gui = false 95 | v.memory = 2048 96 | v.cpus = 2 97 | v.enable_vmrun_ip_lookup = false 98 | v.linked_clone = true 99 | v.vmx["vhv.enable"] = "TRUE" 100 | v.ssh_info_public = true 101 | end 102 | end 103 | 104 | config.vm.provider "virtualbox" do |v, override| 105 | v.gui = false 106 | v.memory = 2048 107 | v.cpus = 2 108 | v.linked_clone = true 109 | # Enable Nested Hardware Virtualisation - requires VirtualBox 6 110 | v.customize ["modifyvm", :id, "--nested-hw-virt", "on"] 111 | # Use the recommended paravirtualization interface for windows (hyperv) - requires VirtualBox 6 112 | v.customize ["modifyvm", :id, "--paravirtprovider", "hyperv"] 113 | end 114 | 115 | config.vm.provider "hyperv" do |v| 116 | v.cpus = 2 117 | v.maxmemory = 2048 118 | v.differencing_disk = true 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /scripts/create-machine.ps1: -------------------------------------------------------------------------------- 1 | param ([String] $machineHome, [String] $machineName, [String] $machineIp, [Switch] $enableLCOW, [Switch] $experimental) 2 | 3 | $ErrorActionPreference = 'Stop'; 4 | 5 | if (!(Test-Path $env:USERPROFILE\.docker)) { 6 | mkdir $env:USERPROFILE\.docker 7 | } 8 | 9 | $ipAddresses = ((Get-NetIPAddress -AddressFamily IPv4).IPAddress) -Join ',' 10 | 11 | if (!$machineIp) { 12 | $machineIp=(Get-NetIPAddress -AddressFamily IPv4 ` 13 | | Where-Object -FilterScript { ` 14 | ( ! ($_.InterfaceAlias).StartsWith("vEthernet (") ) ` 15 | -And $_.IPAddress -Ne "127.0.0.1" ` 16 | -And $_.IPAddress -Ne "10.0.2.15" ` 17 | -And !($_.IPAddress.StartsWith("169.254.")) ` 18 | }).IPAddress 19 | } else { 20 | $ipAddresses = "$ipAddresses,$machineIp" 21 | } 22 | 23 | $homeDir = $machineHome 24 | if ($machineHome.startsWith('/')) { 25 | $homeDir = "C:$machineHome" # /Users/stefan from Mac -> C:/Users/stefan 26 | } 27 | 28 | function ensureDirs($dirs) { 29 | foreach ($dir in $dirs) { 30 | if (!(Test-Path $dir)) { 31 | mkdir $dir 32 | } 33 | } 34 | } 35 | 36 | function installLCOW() { 37 | if (Test-Path "$env:ProgramFiles\Linux Containers") { 38 | Remove-Item -Recurse "$env:ProgramFiles\Linux Containers" 39 | } 40 | Write-Host "`n=== Enable LCOW" 41 | Write-Host " Downloading LCOW LinuxKit ..." 42 | $ProgressPreference = 'SilentlyContinue' 43 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 44 | Invoke-WebRequest -OutFile "$env:TEMP\linuxkit-lcow.zip" "https://github.com/linuxkit/lcow/releases/download/v4.14.35-v0.3.9/release.zip" 45 | Expand-Archive -Path "$env:TEMP\linuxkit-lcow.zip" -DestinationPath "$env:ProgramFiles\Linux Containers" -Force 46 | # if (Test-Path "$env:ProgramFiles\Linux Containers\bootx64.efi") { 47 | # Move-Item "$env:ProgramFiles\Linux Containers\bootx64.efi" "$env:ProgramFiles\Linux Containers\kernel" -Force 48 | # } 49 | Remove-Item "$env:TEMP\linuxkit-lcow.zip" 50 | 51 | # Write-Host " Downloading docker nightly ..." 52 | # Invoke-WebRequest -OutFile "$env:TEMP\docker-master.zip" "https://master.dockerproject.com/windows/x86_64/docker.zip" 53 | # Expand-Archive -Path "$env:TEMP\docker-master.zip" -DestinationPath $env:ProgramFiles -Force 54 | # Remove-Item "$env:TEMP\docker-master.zip" 55 | } 56 | 57 | # https://docs.docker.com/engine/security/https/ 58 | # Thanks to @artisticcheese! https://artisticcheese.wordpress.com/2017/06/10/using-pure-powershell-to-generate-tls-certificates-for-docker-daemon-running-on-windows/ 59 | function createCA($serverCertsPath) { 60 | Write-Host "`n=== Generating CA" 61 | $parms = @{ 62 | type = "Custom" ; 63 | KeyExportPolicy = "Exportable"; 64 | Subject = "CN=Docker TLS Root"; 65 | CertStoreLocation = "Cert:\CurrentUser\My"; 66 | HashAlgorithm = "sha256"; 67 | KeyLength = 4096; 68 | KeyUsage = @("CertSign", "CRLSign"); 69 | TextExtension = @("2.5.29.19 ={critical} {text}ca=1") 70 | } 71 | $rootCert = New-SelfSignedCertificate @parms 72 | 73 | Write-Host "`n=== Generating CA public key" 74 | $parms = @{ 75 | Path = "$serverCertsPath\ca.pem"; 76 | Value = "-----BEGIN CERTIFICATE-----`n" ` 77 | + [System.Convert]::ToBase64String($rootCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) ` 78 | + "`n-----END CERTIFICATE-----"; 79 | Encoding = "ASCII"; 80 | } 81 | Set-Content @parms 82 | return $rootCert 83 | } 84 | 85 | # https://docs.docker.com/engine/security/https/ 86 | function createCerts($rootCert, $serverCertsPath, $serverName, $ipAddresses, $clientCertsPath) { 87 | Write-Host "`n=== Generating Server certificate" 88 | $parms = @{ 89 | CertStoreLocation = "Cert:\CurrentUser\My"; 90 | Signer = $rootCert; 91 | Subject = "CN=serverCert"; 92 | KeyExportPolicy = "Exportable"; 93 | Provider = "Microsoft Enhanced Cryptographic Provider v1.0"; 94 | Type = "SSLServerAuthentication"; 95 | HashAlgorithm = "sha256"; 96 | TextExtension = @("2.5.29.37= {text}1.3.6.1.5.5.7.3.1", "2.5.29.17={text}DNS=$serverName&DNS=localhost&IPAddress=$($ipAddresses.Split(',') -Join '&IPAddress=')"); 97 | KeyLength = 4096; 98 | } 99 | $serverCert = New-SelfSignedCertificate @parms 100 | 101 | $parms = @{ 102 | Path = "$serverCertsPath\server-cert.pem"; 103 | Value = "-----BEGIN CERTIFICATE-----`n" ` 104 | + [System.Convert]::ToBase64String($serverCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) ` 105 | + "`n-----END CERTIFICATE-----"; 106 | Encoding = "Ascii" 107 | } 108 | Set-Content @parms 109 | 110 | Write-Host "`n=== Generating Server private key" 111 | $privateKeyFromCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($serverCert) 112 | $parms = @{ 113 | Path = "$serverCertsPath\server-key.pem"; 114 | Value = ("-----BEGIN PRIVATE KEY-----`n" ` 115 | + [System.Convert]::ToBase64String($privateKeyFromCert.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob), [System.Base64FormattingOptions]::InsertLineBreaks) ` 116 | + "`n-----END PRIVATE KEY-----"); 117 | Encoding = "Ascii"; 118 | } 119 | Set-Content @parms 120 | 121 | Write-Host "`n=== Generating Client certificate" 122 | $parms = @{ 123 | CertStoreLocation = "Cert:\CurrentUser\My"; 124 | Subject = "CN=clientCert"; 125 | Signer = $rootCert ; 126 | KeyExportPolicy = "Exportable"; 127 | Provider = "Microsoft Enhanced Cryptographic Provider v1.0"; 128 | TextExtension = @("2.5.29.37= {text}1.3.6.1.5.5.7.3.2") ; 129 | HashAlgorithm = "sha256"; 130 | KeyLength = 4096; 131 | } 132 | $clientCert = New-SelfSignedCertificate @parms 133 | 134 | $parms = @{ 135 | Path = "$clientCertsPath\cert.pem" ; 136 | Value = ("-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($clientCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END CERTIFICATE-----"); 137 | Encoding = "Ascii"; 138 | } 139 | Set-Content @parms 140 | 141 | Write-Host "`n=== Generating Client key" 142 | $clientprivateKeyFromCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($clientCert) 143 | $parms = @{ 144 | Path = "$clientCertsPath\key.pem"; 145 | Value = ("-----BEGIN PRIVATE KEY-----`n" ` 146 | + [System.Convert]::ToBase64String($clientprivateKeyFromCert.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob), [System.Base64FormattingOptions]::InsertLineBreaks) ` 147 | + "`n-----END PRIVATE KEY-----"); 148 | Encoding = "Ascii"; 149 | } 150 | Set-Content @parms 151 | 152 | copy $serverCertsPath\ca.pem $clientCertsPath\ca.pem 153 | } 154 | 155 | function updateConfig($daemonJson, $serverCertsPath, $enableLCOW, $experimental) { 156 | $config = @{} 157 | if (Test-Path $daemonJson) { 158 | $config = (Get-Content $daemonJson) -join "`n" | ConvertFrom-Json 159 | } 160 | 161 | if (!$experimental) { 162 | $experimental = $false 163 | } 164 | if ($enableLCOW) { 165 | $experimental = $true 166 | } 167 | $config = $config | Add-Member(@{ ` 168 | hosts = @("tcp://0.0.0.0:2376", "npipe://"); ` 169 | tlsverify = $true; ` 170 | tlscacert = "$serverCertsPath\ca.pem"; ` 171 | tlscert = "$serverCertsPath\server-cert.pem"; ` 172 | tlskey = "$serverCertsPath\server-key.pem"; ` 173 | experimental = $experimental ` 174 | }) -Force -PassThru 175 | 176 | Write-Host "`n=== Creating / Updating $daemonJson" 177 | $config | ConvertTo-Json | Set-Content $daemonJson -Encoding Ascii 178 | } 179 | 180 | function createContext ($machineName, $machineHome, $contextMetaPath, $contextCertPath, $machineIp, $serverCertsPath, $clientCertsPath) { 181 | $contextMetaJson = "$contextMetaPath\meta.json" 182 | 183 | $config = @" 184 | { 185 | "Name": "$machineName", 186 | "Metadata": { 187 | "Description": "$machineName windows-docker-machine" 188 | }, 189 | "Endpoints": { 190 | "docker": { 191 | "Host": "tcp://${machineIp}:2376", 192 | "SkipTLSVerify": false 193 | } 194 | } 195 | } 196 | "@ 197 | 198 | Write-Host "`n=== Creating / Updating $machineConfigJson" 199 | $config | Set-Content $contextMetaJson -Encoding Ascii 200 | 201 | Write-Host "`n=== Copying Client certificates to $contextCertPath" 202 | copy $serverCertsPath\ca.pem $contextCertPath\ca.pem 203 | copy $clientCertsPath\cert.pem $contextCertPath\cert.pem 204 | copy $clientCertsPath\key.pem $contextCertPath\key.pem 205 | } 206 | 207 | function createMachineConfig ($machineName, $machineHome, $machinePath, $machineIp, $serverCertsPath, $clientCertsPath) { 208 | $machineConfigJson = "$machinePath\config.json" 209 | 210 | $config = @" 211 | { 212 | "ConfigVersion": 3, 213 | "Driver": { 214 | "IPAddress": "$machineIp", 215 | "MachineName": "$machineName", 216 | "SSHUser": "none", 217 | "SSHPort": 3389, 218 | "SSHKeyPath": "", 219 | "StorePath": "$machineHome/.docker/machine", 220 | "SwarmMaster": false, 221 | "SwarmHost": "", 222 | "SwarmDiscovery": "", 223 | "EnginePort": 2376, 224 | "SSHKey": "" 225 | }, 226 | "DriverName": "generic", 227 | "HostOptions": { 228 | "Driver": "", 229 | "Memory": 0, 230 | "Disk": 0, 231 | "EngineOptions": { 232 | "ArbitraryFlags": [], 233 | "Dns": null, 234 | "GraphDir": "", 235 | "Env": [], 236 | "Ipv6": false, 237 | "InsecureRegistry": [], 238 | "Labels": [], 239 | "LogLevel": "", 240 | "StorageDriver": "", 241 | "SelinuxEnabled": false, 242 | "TlsVerify": true, 243 | "RegistryMirror": [], 244 | "InstallURL": "https://get.docker.com" 245 | }, 246 | "SwarmOptions": { 247 | "IsSwarm": false, 248 | "Address": "", 249 | "Discovery": "", 250 | "Agent": false, 251 | "Master": false, 252 | "Host": "tcp://0.0.0.0:3376", 253 | "Image": "swarm:latest", 254 | "Strategy": "spread", 255 | "Heartbeat": 0, 256 | "Overcommit": 0, 257 | "ArbitraryFlags": [], 258 | "ArbitraryJoinFlags": [], 259 | "Env": null, 260 | "IsExperimental": false 261 | }, 262 | "AuthOptions": { 263 | "CertDir": "$machineHome/.docker/machine/machines/$machineName", 264 | "CaCertPath": "$machineHome/.docker/machine/machines/$machineName/ca.pem", 265 | "CaPrivateKeyPath": "$machineHome/.docker/machine/machines/$machineName/ca-key.pem", 266 | "CaCertRemotePath": "", 267 | "ServerCertPath": "$machineHome/.docker/machine/machines/$machineName/server.pem", 268 | "ServerKeyPath": "$machineHome/.docker/machine/machines/$machineName/server-key.pem", 269 | "ClientKeyPath": "$machineHome/.docker/machine/machines/$machineName/key.pem", 270 | "ServerCertRemotePath": "", 271 | "ServerKeyRemotePath": "", 272 | "ClientCertPath": "$machineHome/.docker/machine/machines/$machineName/cert.pem", 273 | "ServerCertSANs": [], 274 | "StorePath": "$machineHome/.docker/machine/machines/$machineName" 275 | } 276 | }, 277 | "Name": "$machineName" 278 | } 279 | "@ 280 | 281 | Write-Host "`n=== Creating / Updating $machineConfigJson" 282 | $config | Set-Content $machineConfigJson -Encoding Ascii 283 | 284 | Write-Host "`n=== Copying Client certificates to $machinePath" 285 | copy $serverCertsPath\ca.pem $machinePath\ca.pem 286 | copy $clientCertsPath\cert.pem $machinePath\cert.pem 287 | copy $clientCertsPath\key.pem $machinePath\key.pem 288 | } 289 | 290 | $dockerData = "$env:ProgramData\docker" 291 | $userPath = "$env:USERPROFILE\.docker" 292 | 293 | ensureDirs @("$dockerData\certs.d", "$dockerData\config", "$userPath") 294 | 295 | $serverCertsPath = "$dockerData\certs.d" 296 | $clientCertsPath = "$userPath" 297 | $rootCert = createCA "$dockerData\certs.d" 298 | 299 | createCerts $rootCert $serverCertsPath $serverName $ipAddresses $clientCertsPath 300 | updateConfig "$dockerData\config\daemon.json" $serverCertsPath $enableLCOW $experimental 301 | 302 | if ($machineName) { 303 | # write docker-machine configuration file and certs 304 | $machinePath = "$env:USERPROFILE\.docker\machine\machines\$machineName" 305 | ensureDirs @($machinePath) 306 | createMachineConfig $machineName $machineHome $machinePath $machineIp $serverCertsPath $clientCertsPath 307 | Write-Host "`n=== Copying Docker Machine configuration to $homeDir\.docker\machine\machines\$machineName" 308 | if (Test-Path "$homeDir\.docker\machine\machines\$machineName") { 309 | rm -recurse "$homeDir\.docker\machine\machines\$machineName" 310 | } 311 | Copy-Item -Recurse "$env:USERPROFILE\.docker\machine\machines\$machineName" "$homeDir\.docker\machine\machines\$machineName" 312 | 313 | # write docker context configuration file and certs 314 | $ofs = '' 315 | $contextSha = "$(new-object System.Security.Cryptography.SHA256Managed | ForEach-Object {$_.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($machineName))} | ForEach-Object {$_.ToString('x2')})" 316 | $ofs = ' ' 317 | 318 | $contextMetaPath = "$env:USERPROFILE\.docker\contexts\meta\$contextSha" 319 | $contextCertPath = "$env:USERPROFILE\.docker\contexts\tls\$contextSha\docker" 320 | ensureDirs @($contextMetaPath, $contextCertPath) 321 | createContext $machineName $machineHome $contextMetaPath $contextCertPath $machineIp $serverCertsPath $clientCertsPath 322 | 323 | Write-Host "`n=== Copying Docker Context configuration to $homeDir\.docker\contexts\meta\$contextSha" 324 | if (Test-Path "$homeDir\.docker\contexts\meta\$contextSha") { 325 | rm -recurse "$homeDir\.docker\contexts\meta\$contextSha" 326 | } 327 | Copy-Item -Recurse "$env:USERPROFILE\.docker\contexts\meta\$contextSha" "$homeDir\.docker\contexts\meta\$contextSha" 328 | if (Test-Path "$homeDir\.docker\contexts\tls\$contextSha") { 329 | rm -recurse "$homeDir\.docker\contexts\tls\$contextSha" 330 | } 331 | Copy-Item -Recurse "$env:USERPROFILE\.docker\contexts\tls\$contextSha" "$homeDir\.docker\contexts\tls\$contextSha" 332 | } 333 | 334 | Write-Host "Restarting Docker" 335 | Stop-Service docker 336 | dockerd --unregister-service 337 | if ($enableLCOW) { 338 | installLCOW 339 | } 340 | dockerd --register-service 341 | 342 | Start-Service docker 343 | 344 | Write-Host "Opening Docker TLS port" 345 | & netsh advfirewall firewall add rule name="Docker TLS" dir=in action=allow protocol=TCP localport=2376 346 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Windows Docker Machine 2 | [![Build status](https://ci.appveyor.com/api/projects/status/f1i6eotfeghj22u3?svg=true)](https://ci.appveyor.com/project/StefanScherer/windows-docker-machine) 3 | 4 | This Vagrant environment creates a "Docker Machine" to work on your MacBook with 5 | Windows containers. You can easily switch between Docker Desktop Linux 6 | containers and the Windows containers. 7 | 8 | [![Docker Context asciinema](https://asciinema.org/a/8b0NKbNvpKxJhGThjMYCsSNbI.svg)](https://asciinema.org/a/8b0NKbNvpKxJhGThjMYCsSNbI) 9 | 10 | ## Many flavors 11 | 12 | There are several versions of Windows Server. This is where you 13 | decide which Vagrant VM should be started. 14 | 15 | * `2022-box` - Windows Server 2022 (10.0.20348) LTS Channel, ___***prebuilt from Vagrant Cloud***___ 16 | * `2022` - Windows Server 2022 (10.0.20348) LTS Channel 17 | * `2019-box` - Windows Server 2019 (10.0.17763) LTS Channel, ___***prebuilt from Vagrant Cloud***___ 18 | * `2019` - Windows Server 2019 (10.0.17763) LTS Channel 19 | * `1903` - Windows Server, version 1903 (10.0.18362) Semi-Annual Channel 20 | * `1809` - Windows Server, version 1809 (10.0.17763) Semi-Annual Channel 21 | * `1803` - Windows Server, version 1803 (10.0.17134) Semi-Annual Channel 22 | * `2016-box` - Windows Server 2016 (10.0.14393) LTS channel, ___****prebuilt from Vagrant Cloud***___ 23 | * `2016` - Windows Server 2016 (10.0.14393) LTS channel 24 | * `insider` - Windows Server Insider builds 25 | * `lcow` - Windows Server, version 1809 with LCOW enabled 26 | 27 | So with a `vagrant up 2019` you spin up the LTS version, with `vagrant up 1903` 28 | the 1903 semi-annual version and with `vagrant up insider` the Insider build. 29 | 30 | If you don't want to run the **packer** step, you can run `vagrant up 2019-box` 31 | or `vagrant up 2022-box` to get your box image downloaded directly from Vagrant 32 | Cloud. That uses the box images [windows_2019_docker], respectively 33 | [windows_2022_docker]. 34 | 35 | [windows_2019_docker]: https://app.vagrantup.com/StefanScherer/boxes/windows_2019_docker 36 | [windows_2022_docker]: https://app.vagrantup.com/StefanScherer/boxes/windows_2022_docker 37 | 38 | ## Tested environments 39 | 40 | * macOS with Vagrant 2.2.19 41 | * VMware Fusion Pro 11.0.3 42 | * VirtualBox 5.2.26 and 6.1.34 43 | * Windows with Vagrant 2.2.4 44 | * VMware Workstation Pro 15.0.3 45 | * (VirtualBox see issue 46 | [#2](https://github.com/StefanScherer/windows-docker-machine/issues/2)) 47 | * (Hyper-V see issue 48 | [#1](https://github.com/StefanScherer/windows-docker-machine/issues/1)) 49 | 50 | ## Getting started 51 | 52 | First you need a Windows Server VM for your hypervisor. I prefer 53 | "Infrastructure as Code", so every build step is available on GitHub. 54 | 55 | ![packer vagrant docker](images/packer_vagrant_docker.png) 56 | 57 | 1. (optional) **packer build** to build a Vagrant base box, it's like a Docker image, but 58 | for Vagrant VM's. 59 | 2. **vagrant up** to create a running VM instance of Windows Server, either using the 60 | `packer build` or by using one of the pre-built Vagrant Cloud binaries 61 | `2022-box`, `2019-box`, or `2016-box`. 62 | 3. **docker run** to run Windows containers in that Windows VM 63 | 64 | Step 1 (building the headless Vagrant box) can be done with these steps: 65 | 66 | ```bash 67 | $ git clone https://github.com/StefanScherer/packer-windows 68 | $ cd packer-windows 69 | 70 | $ packer build --only=vmware-iso windows_2022_docker.json 71 | $ vagrant box add windows_2022_docker windows_2022_docker_vmware.box 72 | 73 | - or - 74 | 75 | $ packer build --only=vmware-iso windows_2019_docker.json 76 | $ vagrant box add windows_2019_docker windows_2019_docker_vmware.box 77 | 78 | - or - 79 | 80 | $ packer build --only=vmware-iso --var iso_url=~/path-to-1903.iso windows_server_1903_docker.json 81 | $ vagrant box add windows_server_1903_docker windows_server_1903_docker_vmware.box 82 | 83 | - or - 84 | 85 | $ packer build --only=vmware-iso --var iso_url=~/path-to-1809.iso windows_server_1809_docker.json 86 | $ vagrant box add windows_server_1809_docker windows_server_1809_docker_vmware.box 87 | 88 | - or - 89 | 90 | $ packer build --only=vmware-iso --var iso_url=~/path-to-1803.iso windows_server_1803_docker.json 91 | $ vagrant box add windows_server_1803_docker windows_server_1803_docker_vmware.box 92 | 93 | - or - 94 | 95 | $ packer build --only=vmware-iso --var iso_url=~/path-to-insider.iso windows_server_insider_docker.json 96 | $ vagrant box add windows_server_insider_docker windows_server_insider_vmware_docker.box 97 | 98 | - or - 99 | 100 | $ packer build --only=vmware-iso --var iso_url=~/path-to-2016.iso windows_2016_docker.json 101 | $ vagrant box add windows_2016_docker windows_2016_docker_vmware.box 102 | ``` 103 | 104 | Of course you can build only the box version you need. If you are using VirtualBox instead of VMware, 105 | swap `vmware` for `virtualbox` in the vagrant commands above. 106 | 107 | ## Working on macOS & Linux 108 | 109 | ### Create the Docker Machine 110 | 111 | Spin up the headless Vagrant box you created earlier. It will create the TLS 112 | certificates and a corresponding Docker context called `2022-box` or `2019-box`. 113 | 114 | ```bash 115 | $ git clone https://github.com/StefanScherer/windows-docker-machine 116 | $ cd windows-docker-machine 117 | $ vagrant up --provider vmware_desktop 2019-box 118 | 119 | - or - 120 | 121 | $ vagrant up --provider virtualbox 2019-box 122 | ``` 123 | 124 | If you want to use Windows Server 2022, type `2022-box` here instead. 125 | 126 | 127 | ### List your new Docker machine 128 | 129 | ```bash 130 | $ docker context ls 131 | NAME DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR 132 | 2019-box 2019-box windows-docker-machine tcp://192.168.65.130:2376 133 | default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock https://localhost:6443 (default) swarm 134 | dummy tcp://1.2.3.4:2375 135 | ``` 136 | 137 | ### Switch to Windows containers 138 | 139 | ```bash 140 | $ docker context use 2019-box 141 | ``` 142 | 143 | Now your Mac Docker client talks to the Windows Docker engine: 144 | 145 | ```bash 146 | $ docker version 147 | Client: Docker Engine - Community 148 | Version: 19.03.0-beta1 149 | API version: 1.39 (downgraded from 1.40) 150 | Go version: go1.12.1 151 | Git commit: 62240a9 152 | Built: Thu Apr 4 19:15:32 2019 153 | OS/Arch: darwin/amd64 154 | Experimental: false 155 | 156 | Server: Docker Engine - Enterprise 157 | Engine: 158 | Version: 18.09.5 159 | API version: 1.39 (minimum version 1.24) 160 | Go version: go1.10.8 161 | Git commit: be4553c277 162 | Built: 04/11/2019 06:43:04 163 | OS/Arch: windows/amd64 164 | Experimental: false 165 | ``` 166 | 167 | ### Switch back to Docker Desktop 168 | 169 | ```bash 170 | $ docker context use default 171 | ``` 172 | 173 | This removes all DOCKER environment variables and you can use your Docker for 174 | Mac installation. 175 | 176 | ```bash 177 | $ docker version 178 | Client: Docker Engine - Community 179 | Version: 19.03.0-beta1 180 | API version: 1.39 (downgraded from 1.40) 181 | Go version: go1.12.1 182 | Git commit: 62240a9 183 | Built: Thu Apr 4 19:15:32 2019 184 | OS/Arch: darwin/amd64 185 | Experimental: false 186 | 187 | Server: Docker Engine - Community 188 | Engine: 189 | Version: 18.09.2 190 | API version: 1.39 (minimum version 1.12) 191 | Go version: go1.10.6 192 | Git commit: 6247962 193 | Built: Sun Feb 10 04:13:06 2019 194 | OS/Arch: linux/amd64 195 | Experimental: false 196 | ``` 197 | 198 | ### Mounting volumes from your Mac machine 199 | 200 | Just use `C:$(pwd)` to prepend a drive letter. 201 | 202 | ```bash 203 | $ docker run -it -v C:$(pwd):C:$(pwd) mcr.microsoft.com/windows/servercore:1809 powershell 204 | ``` 205 | 206 | This mounts the current working directory through the Windows VM into the 207 | Windows Container. 208 | 209 | ### Accessing published ports of Windows containers 210 | 211 | When you run Windows containers with publish ports then you can use the IP 212 | address of the Windows Docker host to access it. The `docker context` command in combination with `jq` can give your the IP address with a command. Alternatively `docker-machine ip` also gives you the IP address. 213 | 214 | Example: Run the whoami Windows container and open it in the default macOS 215 | browser. 216 | 217 | ``` 218 | $ docker run -d -p 8080:8080 stefanscherer/whoami 219 | $ open http://$(docker context inspect 2019-box | jq -r '.[0].Endpoints.docker.Host | .[6:] | .[:-5]'):8080 220 | 221 | - or - 222 | 223 | $ open http://$(docker-machine ip 2019-box):8080 224 | ``` 225 | 226 | ## Working on Windows 227 | 228 | Spin up the headless Vagrant box you created earlier. It will create the TLS 229 | certificates and a corresponding Docker context called `2022-box` or `2019-box`. 230 | 231 | If you haven't worked with `docker context` yet, create the `.docker` directory 232 | in your user profile manually. 233 | 234 | ```powershell 235 | PS C:\> mkdir $env:USERPROFILE\.docker 236 | ``` 237 | 238 | ### Create the Docker Machine 239 | 240 | Choose your hypervisor and start the VM 241 | 242 | ```powershell 243 | PS C:\> git clone https://github.com/StefanScherer/windows-docker-machine 244 | PS C:\> cd windows-docker-machine 245 | PS C:\> vagrant up --provider vmware_desktop 2019-box 246 | 247 | - or - 248 | 249 | PS C:\> vagrant up --provider virtualbox 2019-box 250 | 251 | - or - 252 | 253 | PS C:\> vagrant up --provider hyperv 2019-box 254 | ``` 255 | 256 | If you want to use Windows Server 2022, type `2022-box` here instead. 257 | 258 | Notice: The provider `hyperv` does mount the volumes with SMB into the Windows Server 259 | VM. It seems that there is a problem mounting that further into a Windows 260 | container. The provisioning (creating the TLS certs and copying them back to the 261 | Windows host) will fail. 262 | 263 | ### List your new Docker machine 264 | 265 | ```powershell 266 | PS C:\> docker context ls 267 | NAME DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR 268 | 2019-box 2019-box windows-docker-machine tcp://192.168.65.130:2376 269 | default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock https://localhost:6443 (default) swarm 270 | ``` 271 | 272 | ### Switch to Windows containers 273 | 274 | ```powershell 275 | PS C:\> docker context use 2019-box 276 | ``` 277 | 278 | Now your Windows Docker client talks to the Windows Docker engine: 279 | 280 | ```powershell 281 | PS C:\> docker version 282 | Client: Docker Engine - Community 283 | Version: 19.03.0-beta1 284 | API version: 1.39 (downgraded from 1.40) 285 | Go version: go1.12.1 286 | Git commit: 62240a9 287 | Built: Thu Apr 4 19:15:32 2019 288 | OS/Arch: darwin/amd64 289 | Experimental: false 290 | 291 | Server: Docker Engine - Enterprise 292 | Engine: 293 | Version: 18.09.5 294 | API version: 1.39 (minimum version 1.24) 295 | Go version: go1.10.8 296 | Git commit: be4553c277 297 | Built: 04/11/2019 06:43:04 298 | OS/Arch: windows/amd64 299 | Experimental: false 300 | ``` 301 | 302 | ### Switch to back to Docker for Windows 303 | 304 | ```powershell 305 | PS C:\> docker context use default 306 | ``` 307 | 308 | This removes all DOCKER environment variables and you can use your Docker for 309 | Windows installation. 310 | 311 | ```powershell 312 | PS C:\> docker version 313 | Client: Docker Engine - Community 314 | Version: 19.03.0-beta1 315 | API version: 1.39 (downgraded from 1.40) 316 | Go version: go1.12.1 317 | Git commit: 62240a9 318 | Built: Thu Apr 4 19:15:32 2019 319 | OS/Arch: darwin/amd64 320 | Experimental: false 321 | 322 | Server: Docker Engine - Community 323 | Engine: 324 | Version: 18.09.2 325 | API version: 1.39 (minimum version 1.12) 326 | Go version: go1.10.6 327 | Git commit: 6247962 328 | Built: Sun Feb 10 04:13:06 2019 329 | OS/Arch: linux/amd64 330 | Experimental: false 331 | 332 | ``` 333 | 334 | ### Mounting volumes from your Windows machine 335 | 336 | Just use `$(pwd)` in PowerShell. 337 | 338 | ```powershell 339 | PS C:\> docker run -it -v "$(pwd):$(pwd)" mcr.microsoft.com/windows/servercore:1809 powershell 340 | ``` 341 | 342 | This mounts the current working directory through the Windows VM into the 343 | Windows Container. 344 | 345 | ### Accessing published ports of Windows containers 346 | 347 | When you run Windows containers with publish ports then you can use the IP 348 | address of the Windows Docker host to access it. The `docker context inspect` command can 349 | give your the IP address with a command. 350 | 351 | Example: Run the whoami Windows container and open it in the default browser. 352 | 353 | ```powershell 354 | PS C:\> docker run -d -p 8080:8080 stefanscherer/whoami 355 | PS C:\> start http://$(docker-machine ip 2019-box):8080 356 | ``` 357 | 358 | ## Further commands 359 | 360 | Here is a list of `vagrant` and `docker` commands for typical actions. 361 | I use a `bash` function 362 | [`dm` in my dotfiles repo](https://github.com/StefanScherer/dotfiles/blob/4517216a56708acad5c2c0809f6bfafbd880aea3/.functions#L140-L182) 363 | to simplify all the tasks without switching to the Vagrant folder each time. 364 | The `dm` started as a shortcut for `docker-machine` commands. I have [updated the function](https://github.com/StefanScherer/dotfiles/commit/2e771b023d97d9ec91fb20633204903c9f3b21eb#diff-e39ded4129d0efa321423853506c0116) to work with `docker context`, but rolled back for now as I prefer the environment variables to have different "contexts" per terminal tab. 365 | 366 | | dm shortcut | Vagrant / Docker command | 367 | | ------------------------------ | ------------------------------------ | 368 | | `dm start 2019-box` | `vagrant up --provider xxx 2019-box` | 369 | | `dm regenerate-certs 2019-box` | `vagrant provision 2019-box` | 370 | | `dm stop 2019-box` | `vagrant halt 2019-box` | 371 | | `dm start 2019-box` | `vagrant up 2019-box` | 372 | | `dm rdp 2019-box` | `vagrant rdp 2019-box` | 373 | | `dm rm [-f] 2019-box` | `vagrant destroy [-f] 2019-box` | 374 | | `dm 2019-box` | `docker context use 2019-box` or
`eval $(docker-machine env 2019-box)` | 375 | | `dm ip 2019-box` | docker context inspect 2019-box | jq -r '.[0].Endpoints.docker.Host | .[6:] | .[:-5]' or
`docker-machine ip 2019-box` | 376 | 377 | ## Insider builds 378 | 379 | If you want to follow the Windows Server Insider builds then this is for you. It 380 | is tested on a Mac with the following steps. 381 | 382 | 1. Register at Windows Insider program https://insider.windows.com 383 | 384 | 2. Download the Windows Server ISO from 385 | https://www.microsoft.com/en-us/software-download/windowsinsiderpreviewserver?wa=wsignin1.0 386 | 387 | 3. Build the Vagrant basebox with Packer 388 | 389 | ```bash 390 | git clone https://github.com/StefanScherer/packer-windows 391 | cd packer-windows 392 | packer build --only=vmware-iso --var iso_url=~/Downloads/Windows_InsiderPreview_Server_en-us_18356.iso windows_server_insider_docker.json 393 | vagrant box add windows_server_insider_docker windows_server_insider_docker_vmware.box 394 | ``` 395 | 396 | Then spin up your Insider machine with 397 | 398 | ``` 399 | vagrant up insider 400 | ``` 401 | 402 | This Vagrant box has Docker installed and the following base 403 | images are already pulled from Docker Hub: 404 | 405 | * mcr.microsoft.com/windows/servercore/insider 406 | * mcr.microsoft.com/windows/nanoserver/insider 407 | 408 | ## LCOW 409 | 410 | You can try the Linux Container on Windows feature in a separate machine `lcow`. 411 | It is preconfigured to use the Windows Server, version 1903. But you can 412 | also use Windows Insider Server Preview as base box. 413 | 414 | ``` 415 | vagrant up lcow 416 | docker context use lcow 417 | docker run alpine uname -a 418 | ``` 419 | 420 | ## Cleanup 421 | 422 | If you want to cleanup your machine again after playing with Windows Containers, use the following commands 423 | 424 | ``` 425 | vagrant destroy -f 426 | vagrant box remove StefanScherer/windows_2019_docker 427 | docker context rm 2019-box 428 | ``` 429 | --------------------------------------------------------------------------------