├── .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 | [](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 | [](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 | 
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 |
--------------------------------------------------------------------------------