Wilson Mar bio photo

Wilson Mar

Hello. Hire me!

Email me Calendar Skype call 310 320-7878

LinkedIn Twitter Gitter Google+ Youtube

Github Stackoverflow Pinterest

Make your own Windows 2016 Docker images to run within a Mac laptop


This tutorial contains a hands-on step-by-step instructions for “newbies” to create a Docker image that runs Windows 2016 server within a Mac laptop (the host machine).


The workflow begins from “scratch” with an ISO file (like on a DVD) from Microsoft. Microsoft requires registration. We download Stefan’s repo from GitHub, which has a Packer script that uses the iso file to create a VMware Fusion server image running on MacOS. VMware is needed for nested virtualization of Windows HyperV containers. After applying Windows Update, the VMware image is converted to a Vagrant box image. Based on Hashicorp’s VMWare-vagrant provider and the Vagrantfile, a Windows server is instantiated. We RDP into the Vagrant box to install and configure Windows apps. This Windows server is used as the host to create Dockerized images such as Windows Docker Containers. The images are pushed up to DockerHub so others can use it to run as isolated on each Windows server machine in production.

This is a companion to “Docker setup” and based on https://github.com/StefanScherer/docker-windows-box/

  1. Be inside a Terminal window, on any directory.
  2. Install Homebrew
  3. Install a Git client.
  4. ssh.exe or putty for Windows.


    Install VMware Fusion 8.5 for Mac (not the Pro edition) for its drivers.

  5. Pay $395 for the license after 10 days.
  6. Get the installer from:


    VMware-Fusion-8.5.8-5824040.dmg is 467 MB.

  7. Double-click on the file downloaded to run the installer.
  8. Exit the pop-up by clicking the “X” at the upper-left.
  9. In Finder, scroll to the “Drives” section on the left pane to click the exit icon to the right of “VMware Fusion”.
  10. Right-click the .dmg and “Move to Trash”.

    PROTIP: There is no need to invoke the program from Applications folder (/Applications/VMware Fusion.app).


    Vagrant install

  11. Install Vagrant using Homebrew:

    export HOMEBREW_CASK_OPTS=”–appdir=/Applications” brew cask install vagrant

  12. To avoid issues, delete the curl program from Vagrant so your Mac’s program is used:

    sudo rm /opt/vagrant/embedded/bin/curl

    Vagrant VMware install

  13. Install the Vagrant Fusion plugin from Hashicorp:

    vagrant plugin install vagrant-vmware-fusion

    The response at time of writing:

    Installing the 'vagrant-vmware-fusion' plugin. This can take a few minutes...
    Fetching: vagrant-share-1.1.9.gem (100%)
    Fetching: vagrant-vmware-fusion-4.0.24.gem (100%)
    Installed the plugin 'vagrant-vmware-fusion (4.0.24)'!
  14. Pay $79 for the license at https://www.vagrantup.com/vmware/index.html

    Vagrant plugin usage documentation is at: https://docs.vagrantup.com/v2/plugins/usage.html

  15. Open the email from Hashicorp. Click the link. Click “click here” to download the license.lic file.
  16. Construct the command with the name of the license file:

    vagrant plugin license vagrant-vmware-fusion ~/Downloads/hashicorp-vagrant-license.lic

    The response:

    Installing license for 'vagrant-vmware-fusion'...
    The license for 'vagrant-vmware-fusion' was successfully installed!

    QUESTION: What is the name of the provider to use?

    Packer from GitHub

    Packer (at packer.io) is from Hashicorp who created Vagrant. It is a cross-vendor utility that builds images used by VMware and cloud providers: Amazon EC2, CloudStack, DigitalOcean, Docker, Google Compute Engine, Microsoft Azure, QEMU, VirtualBox.

  17. Install Packer using Homebrew:

    brew install packer

  18. Create a folder to house various repositories from GitHub:

    cd ~
    mkdir gits
    cd gits

    Alternately, some prefer dev.

  19. Create a folder to house repositories around a subject, such as virtual machines:

    cd ~/gits
    mkdir vms
    cd vms

    Alternately, use your own “subject” folder.

  20. Get the Packer scripts (just the latest version):

    git clone https://github.com/StefanScherer/packer-windows --depth=1
    cd packer-windows 

    Sample response:

    Cloning into 'packer-windows'...
    remote: Counting objects: 167, done.
    remote: Compressing objects: 100% (133/133), done.
    remote: Total 167 (delta 42), reused 93 (delta 11), pack-reused 0
    Receiving objects: 100% (167/167), 130.58 KiB | 0 bytes/s, done.
    Resolving deltas: 100% (42/42), done.
  21. Use a text editor to view file windows_2016_docker.json.

    The sections are “builder”, “provisioners”, “Post-processors”, and “variables”.

    The “iso_url” variable defines the URL to get the Windows 2016 disk image.

    WARNING: The URL changes over time.

    Register for Windows ISO file

  22. Get to the page providing downloads of Windows Server 2016 Evaluation edition good for 180 days:


    We come to this page to get a cookie for a Packer script to download.

  23. Click Register, and provide your info.
  24. Select “ISO” (not Azure or Virtua Lab). Click Continue.
  25. Select “English”. Click Download.
  26. Cancel the download of file:


    Leave it to the Packer to download the file, but we want to make sure that it downloads the most recent file Microsoft has.

    PROTIP: The file at time of writing is a whopping 6.5 GB! So it may take hours to download, depending on how fast your internet connection is.

    Packer pulls the installer and runs it

  27. While in the packer-windows folder, use the Packer file windows_2016_docker.json in the repo from Stefan:

    packer build --only=vmware-iso windows_2016_docker.json

    This downloads the .iso file from Microsoft. Note one of the response lines from it:

    vmware-iso: Downloading or copying: http://care.dlservice.microsoft.com/dl/download/1/4/9/149D5452-9B29-4274-B6B3-5361DBDA30BC/14393.0.161119-1705.RS1_REFRESH_SERVER_EVAL_X64FRE_EN-US.ISO

    Packer stores the installer into a folder named “packer_cache”. So to run using what has already been downloaded:

    packer build --only=vmware-iso windows_2016_docker.json --var iso_url=~/gits/vms/packer-windows/packer_cache/49f719e23c56a779a991c4b4ad1680b8363918cd0bfd9ac6b52697d78a309855.iso

    NOTE: This is also MSDN file en_windows_server_2016_x64_dvd_9718492.iso

    PROTIP: The Packer command can be restarted and it restarts where it left off on partial downloads. Once downloaded, it knows to not download again.

    The rest of the response:

     vmware-iso: Download progress: 100%
    ==> vmware-iso: Creating floppy disk...
     vmware-iso: Copying files flatly from floppy_files
     vmware-iso: Copying file: ./answer_files/2016/Autounattend.xml
     vmware-iso: Copying file: ./floppy/WindowsPowershell.lnk
     vmware-iso: Copying file: ./floppy/PinTo10.exe
     vmware-iso: Copying file: ./scripts/disable-screensaver.ps1
     vmware-iso: Copying file: ./scripts/disable-winrm.ps1
     vmware-iso: Copying file: ./scripts/docker/enable-winrm.ps1
     vmware-iso: Copying file: ./scripts/docker/2016/install-containers-feature.ps1
     vmware-iso: Copying file: ./scripts/microsoft-updates.bat
     vmware-iso: Copying file: ./scripts/win-updates.ps1
     vmware-iso: Done copying files from floppy_files
     vmware-iso: Collecting paths from floppy_dirs
     vmware-iso: Resulting paths from floppy_dirs : []
     vmware-iso: Done copying paths from floppy_dirs
    ==> vmware-iso: Creating virtual machine disk
    ==> vmware-iso: Building and writing VMX file
    ==> vmware-iso: Starting virtual machine...

    VMware Fusion Internal Error

    On occassion, my VMware Fusion 8.5 displays “Internal error” upon starting an image.

    ==> vmware-iso: Error starting VM: VMware error: Error: Cannot connect to the virtual machine
    ==> vmware-iso: Deleting output directory...
    Build 'vmware-iso' errored: Error starting VM: VMware error: Error: Cannot connect to the virtual machine
    ==> Some builds didn't complete successfully and had errors:
    --> vmware-iso: Error starting VM: VMware error: Error: Cannot connect to the virtual machine
    ==> Builds finished but no artifacts were created.

    PROTIP: The Packer command can be restarted and it restarts where it left off

    Windows Desktop Update

    Packer shows this message while it constructs a Windows instance:

    ==> vmware-iso: Waiting for WinRM to become available...

    A cmd.exe window appears to display messages about the Update process, which can take a while.

    Checking for Windows Updates
    Script: A:\win-updates.ps1
    Script User: VAGRANT-2016\vagrant
    Started: 9/18/2017 12:30:58 AM

    We wait (with no countdown UI) while this appears:

    Downloading updates...

    WARNING: If Windows Update is interrupted, Packer will start again with a new instance of VMware Fusion image, and download again.

    TODO: Contact them and see if we can avoid re-downloading.

  28. PROTIP: Keep your laptop plugged into a power source and a (preferrably fast) network.

  29. PROTIP: Keep the MacOS screen from going to sleep: choose Apple menu > System Preferences, click Energy Saver. Drag the “Display sleep” to “Never”.

    PROTIP: To move mouse beyond the Windows machine, press command+Tab until the process you want is highlighted.

    The WindowsServer2016Docker.vmx file that VMware Fusion opens is created inside the output-vmware-iso folder. However, this folder is deleted when you finally see:

    Build 'vmware-iso' finished.
    ==> Build finished. The artifacts of successful builds are:
    --> vmware-iso: 'vmware' provider box: windows_2016_docker_vmware.box

    The .box file is in the GitHub root level directory.

    Custom applications are added within the Vagrant image, not in the .vmx image.


    PROTIP: Do not generate a Vagrantfile using a command such as:

    vagrant box add windows_2016_docker windows_2016_docker_vmware.box
  30. Copy the vagrantfile-windows_2016.template to Vagrantfile:

    cp vagrantfile-windows_2016.template Vagrantfile
  31. Edit the file to reference the name of the box file in the vagrant add above</a>:

     config.vm.define "windows_2016_docker"
     config.vm.box = "windows_2016_docker_vmware.box"
  32. Have Vagrant bring up the server instance:

    vagrant up

    The response:

==> windows_2016_docker: Box ‘windows_2016_docker_vmware.box’ could not be found. Attempting to find and install… windows_2016_docker: Box Provider: virtualbox windows_2016_docker: Box Version: >= 0 ==> windows_2016_docker: Box file was not detected as metadata. Adding it directly… ==> windows_2016_docker: Adding box ‘windows_2016_docker_vmware.box’ (v0) for provider: virtualbox windows_2016_docker: Unpacking necessary files from: file:///Users/wilsonmar/gits/vms/packer-windows/windows_2016_docker_vmware.box windows_2016_docker: Progress: 0% (Rate: 0/s, Estimated time remaining: –:- windows_2016_docker: Progress: 0% (Rate: 419M/s, Estimated time remaining: 0 windows_2016_docker: Progress: 8% (Rate: 453M/s, Estimated time remaining: 0 windows_2016_docker: Progress: 18% (Rate: 493M/s, Estimated time remaining: windows_2016_docker: Progress: 26% (Rate: 477M/s, Estimated time remaining: windows_2016_docker: Progress: 36% (Rate: 492M/s, Estimated time remaining: windows_2016_docker: Progress: 48% (Rate: 519M/s, Estimated time remaining: windows_2016_docker: Progress: 59% (Rate: 548M/s, Estimated time remaining: windows_2016_docker: Progress: 70% (Rate: 562M/s, Estimated time remaining: windows_2016_docker: Progress: 82% (Rate: 596M/s, Estimated time remaining: windows_2016_docker: Progress: 92% (Rate: 596M/s, Estimated time remaining:

 detected as metadata. Adding it directly...
==> box: Adding box 'windows_2016_docker' (v0) for provider: 
    box: Unpacking necessary files from: file:///Users/wilsonmar/gits/vms/packer-windows/windows_2016_docker_vmware.box
==> box: Successfully added box 'windows_2016_docker' (v0) for 'vmware_desktop'!

PROTIP: The above command adds to folder ~/.vagrant.d/boxes/ under your user home.

  1. List Vagrant boxes (to verify):

    vagrant box list

    The response should be:

    windows_2016_docker (vmware_desktop, 0)
  2. List Vagrant commands:



    Usage: vagrant [options] command> [args>]
     -v, --version                    Print the version and exit.
     -h, --help                       Print this help.
    Common commands:
      box             manages boxes: installation, removal, etc.
      connect         connect to a remotely shared Vagrant environment
      destroy         stops and deletes all traces of the vagrant machine
      global-status   outputs status Vagrant environments for this user
      halt            stops the vagrant machine
      help            shows the help for a subcommand
      init            initializes a new Vagrant environment by creating a Vagrantfile
      login           log in to HashiCorp's Vagrant Cloud
      package         packages a running vagrant environment into a box
      plugin          manages plugins: install, uninstall, update, etc.
      port            displays information about guest port mappings
      powershell      connects to machine via powershell remoting
      provision       provisions the vagrant machine
      push            deploys code in this environment to a configured destination
      rdp             connects to machine via RDP
      reload          restarts vagrant machine, loads new Vagrantfile configuration
      resume          resume a suspended vagrant machine
      share           share your Vagrant environment with anyone in the world
      snapshot        manages snapshots: saving, restoring, etc.
      ssh             connects to machine via SSH
      ssh-config      outputs OpenSSH valid configuration to connect to the machine
      status          outputs status of the vagrant machine
      suspend         suspends the machine
      up              starts and provisions the vagrant environment
      validate        validates the Vagrantfile
      version         prints current and latest Vagrant version
    For help on any individual command run `vagrant COMMAND -h`
    Additional subcommands are available, but are either more advanced
    or not commonly used. To see all subcommands, run the command
    `vagrant list-commands`.
  3. Use a text editor to view the Vagrantfile provided in the repo:

    Vagrant.configure("2") do |config|
      # Wait max 10 minutes (600 seconds) to start VM without provisioning:
      config.vm.boot_timeout = 600

config.vm.provider :virtualbox do |p| p.name = ‘windows_2016_docker’ config.vm.box_url = “windows_2016_docker_vmware.box” end end </pre>

CAUTION: Do not issue command vagrant init which overwrites the Vagrantfile provided.

  1. Bring up Vagrant .box in the folder:

    vagrant up --provider vmware_fusion

    Example response:

    Bringing machine 'default' up with 'virtualbox' provider...
    ==> default: Box 'windows_2016_docker' could not be found. Attempting to find and install...
     default: Box Provider: virtualbox
     default: Box Version: >= 0
    ==> default: Box file was not detected as metadata. Adding it directly...
    ==> default: Adding box 'windows_2016_docker' (v0) for provider: virtualbox
     default: Unpacking necessary files from: file:///Users/wilsonmar/gits/vms/packer-windows/windows_2016_docker_vmware.box

    CAUTION: Do not stop the window (by pressing congtrol+C) unless you want to.

  2. Get the id:

    vagrant global-status

  3. Open another Terminal window to login to the new server via SSH (Secure Shell):

    vagrant rdp

    Custom apps install

    Once your instance is created, you will likely want to make customizations, such as adding applications.

    PROTIP: Make a copy of it before making changes. This takes several Gigabytes.


    Windows Containers

    Microsoft’s Windows Containers provide operating system level virtualization that allows multiple isolated applications (within Docker containers) that run on a single machine.


    • https://www.youtube.com/watch?v=N7SG2wEyQtM Windows Containers and Docker: 101

    • https://www.youtube.com/watch?v=tm5Gw1rIg-M Tutorial - Docker Overview for .NET Developers (1/7) Microsoft Visual Studio 16K views

    Getting Started with Docker on Windows 10 [10:27] by Cloud Academy

    https://www.youtube.com/watch?v=CE9StJpb2ZY”> Windows Containers with Docker</a> [11:12] published on May 31, 2016 by TechBusters IT Solutions

### Windows Server

  1. Select “Server with Desktop Experience” (not Server Core) to get the Desktop GUI.

  2. Create WS 2016 TP5 VM in virtualbox

  3. In the new VM, run this:


    Now you can run Windows Containers in the VM. To make the setup a little easier to use, see this:



  1. Put it into Docker Hub.

Add Hyper-V support to 2016 TP5 Docker VM



A more up to date guide Getting started with Windows Containers by @glennsarti



Build command

  1. View the build command which carries out specifications in Dockerfile to package an image to run within Docker containers:

    docker build --help

    The response:

    Usage:   docker build [OPTIONS] PATH | URL | -
    Build an image from a Dockerfile
       --build-arg value         Set build-time variables (default [])
       --cgroup-parent string    Optional parent cgroup for the container
       --cpu-period int          Limit the CPU CFS (Completely Fair Scheduler) period
       --cpu-quota int           Limit the CPU CFS (Completely Fair Scheduler) quota
      -c, --cpu-shares int          CPU shares (relative weight)
       --cpuset-cpus string      CPUs in which to allow execution (0-3, 0,1)
       --cpuset-mems string      MEMs in which to allow execution (0-3, 0,1)
       --disable-content-trust   Skip image verification (default true)
      -f, --file string             Name of the Dockerfile (Default is 'PATH/Dockerfile')
       --force-rm                Always remove intermediate containers
       --help                    Print usage
       --isolation string        Container isolation technology
       --label value             Set metadata for an image (default [])
      -m, --memory string           Memory limit
       --memory-swap string      Swap limit equal to memory plus swap: '-1' to enable unlimited swap
       --no-cache                Do not use cache when building the image
       --pull                    Always attempt to pull a newer version of the image
      -q, --quiet                   Suppress the build output and print image ID on success
       --rm                      Remove intermediate containers after a successful build (default true)
       --shm-size string         Size of /dev/shm, default value is 64MB
      -t, --tag value               Name and optionally a tag in the 'name:tag' format (default [])
       --ulimit value            Ulimit options (default [])
  2. Change directory to the present working directory of the app folder containing a Dockerfile.

    Alternately, the folder path to read can be specified by the “-f” parameter.

  3. Build an image referencing the Dockerfile in the present working directory:

    sudo docker build -t dv:02.01 .

    NOTE: A semi-colon separates the REPOSITORY name from the TAG (“02.01”).

    PROTIP: It’s best practice to tag images by specifying a name after the -t flag.

    WARNING: There is a dot at the end of the command to designate the local folder.

  4. After providing the password, the response is like:

    Sending build context to Docker daemon 102.4 kB
    Step 1 : FROM node:0.10.44-slim
    0.10.44-slim: Pulling from library/node
    8b87079b7a06: Pull complete 
    a3ed95caeb02: Pull complete 
    1bb8eaf3d643: Pull complete 
    5674f5dccbc4: Pull complete 
    b63eb91619dc: Pull complete 
    Digest: sha256:c1ddf2b7d80c66ba8883601ef6a5e201ac2b855b87b6589a023ca889aec24204
    Status: Downloaded newer image for node:0.10.44-slim
     ---> f73347dab179
    Step 2 : ADD . /home/demo/box/
     ---> 812aefdf5cd4
    Removing intermediate container 26d14d6cc382
    Step 3 : RUN cd /home/demo/box && npm install
     ---> Running in 4f85900e5796
    underscore@1.8.3 node_modules/underscore
    async@2.0.1 node_modules/async
    └── lodash@4.14.0
    mongoose@4.5.7 node_modules/mongoose
    ├── sliced@1.0.1
    ├── ms@0.7.1
    ├── regexp-clone@0.0.1
    ├── muri@1.1.0
    ├── hooks-fixed@1.2.0
    ├── mpromise@0.5.5
    ├── kareem@1.1.3
    ├── mpath@0.2.1
    ├── async@1.5.2
    ├── bson@0.4.23
    ├── mquery@1.11.0 (sliced@0.0.5, debug@2.2.0, bluebird@2.10.2)
    └── mongodb@2.1.18 (es6-promise@3.0.2, readable-stream@1.0.31, mongodb-core@1.3.18)
     ---> 1780349c4f38
    Removing intermediate container 4f85900e5796
    Step 4 : ENTRYPOINT /home/demo/box/boot.sh
     ---> Running in f448f0fed4ec
     ---> 5bb9d72b9e60
    Removing intermediate container f448f0fed4ec
    Successfully built 5bb9d72b9e60

    Wait for status to go from “Downloading” to “Pull complete”.


  1. Define a base docker-compose.yml to create a new configuration.

  2. Define a production.yml file to selectively override specific the original Compose file.

  3. Override the base file with elements of the production.yml file:

    docker-compose -f docker-compose.yml -f production.yml up -d

    See https://docs.docker.com/v1.11/compose/extends/#different-environments

Add Webhooks to Docker Hub

  1. Click the Webhooks tab.
  2. Click the image under CREATE A WEBHOOK (which turns to “+”).
  3. Type a webhook name. It must be more than 2 characters long.
  4. Specify the Webhook URL.

    NOTE: Do not check “When Active, new pushes will trigger automatic builds”

  5. Click Create.
  6. Activate build trigger.
  7. Highlight the contents of field Trigger URL containing a string such as:


    Your link would have something else than “user/repo” and the GUID at the end.

  8. Copy it to Clipboard.

  9. Switch to your CI tool (Jenkins), create new Environment Variable.
  10. Name each variable. paste it.

    Set Docker Trigger URL

  11. Switch to your CI tool (Jenkins).
  12. Define a new enviornment variable such as “DOCKER_HUB_TRIGGER”.
  13. Construct in its value field this:

    curl -H “Content-Type:application/json” –data ‘{“build”:true}’ -X POST TRIGGER_URL

  14. Switch to Docker Hub to copy


See “Docker Explained: Using Dockerfiles to Automate Building of Images”a>





More on DevOps

This is one of a series on DevOps:

  1. DevOps_2.0
  2. ci-cd (Continuous Integration and Continuous Delivery)
  3. User Stories for DevOps

  4. Git and GitHub vs File Archival
  5. Git Commands and Statuses
  6. Git Commit, Tag, Push
  7. Git Utilities
  8. Data Security GitHub
  9. GitHub API
  10. TFS vs. GitHub

  11. Choices for DevOps Technologies
  12. Java DevOps Workflow
  13. AWS DevOps (CodeCommit, CodePipeline, CodeDeploy)
  14. AWS server deployment options

  15. Digital Ocean
  16. Cloud regions
  17. AWS Virtual Private Cloud
  18. Azure Cloud Onramp
  19. Azure Cloud
  20. Azure Cloud Powershell

  21. Powershell Ecosystem
  22. Powershell on MacOS
  23. Powershell Desired System Configuration

  24. Jenkins Server Setup
  25. Jenkins Plug-ins
  26. Jenkins Freestyle jobs
  27. Jenkins2 Pipeline jobs using Groovy code in Jenkinsfile

  28. Dockerize apps
  29. Docker Setup
  30. Docker Build

  31. Maven on MacOSX

  32. Ansible

  33. MySQL Setup

  34. SonarQube static code scan

  35. API Management Microsoft
  36. API Management Amazon

  37. Scenarios for load