Why should you run Sitecore in a Docker instance?

There are several solid reasons to run Sitecore in a Docker instance:

  1. It’s quick to stand up once the initial container is created
  2. It’s easy to roll out and remove as needed to meet scaling demands
  3. The Docker tool sets help management, migration, and monitoring
  4. It allows for a clean deployment process for platforms that support Docker
  5. Non-Sitecore developers can create a working local instance with ease
  6. It creates consistency in rollouts, snapshots, and production environments

At GeekHive, we’ve found a huge benefit to running Sitecore in a Docker instance: it speeds up time to development by providing the ability to provision an entire Sitecore instance thru Docker quickly and efficiently. If you’re ready to give it a shot, here’s a step by step breakdown of how to do it!

STEP 1 - Setup your host environment

In order to run Sitecore in a container you will need a host server running Windows Server 2016 Tech Preview 4 or later. As an alternative, if you already have one on locally, feel free to use it. For expedience sake I’m going to base my instructions on a new Virtual Machine (VM) running on Azure using this base image from Microsoft: https://azure.microsoft.com/en-us/marketplace/partners/microsoft/windowsserver2016technicalpreviewwithcontainers/

Create a new Azure VM

Provision a new VM. Select the “Windows Server 2016 Core with Containers Tech Preview 4” image.

While we’re in the Azure control panel, add an endpoint for HTTP traffic over port 80. It will come in useful later when we want to access the instance.

Alternate: Bring your own server

If you wish to go the local route, please note that the host OS must be at least core (not nano) and have the container feature enabled. Microsoft has supplied a PowerShell script to make this easier which you can get here: https://aka.ms/tp4/Install-ContainerHost

Also note, that if your container host is itself a VM, your environment will need to support nested virtualization. Setting this up is outside the scope of this article and if you don’t know what this means the bare metal or Azure VMs will likely be an easier route for you. If you’re feeling adventurous, you can learn more about enabling this in a Hyper-V environment here: https://msdn.microsoft.com/en-us/virtualization/hyperv_on_windows/user_guide/nesting

For clarity and consistency, from this point in the article forward the instructions will be assuming you’ve gone the Azure VM route. You should be able to follow along with your own local server too, but some of the instructions will need to be adapted.

Install 7-zip

We’re going to need a utility to work with archives. 7-zip is my tool of choice, and I opted to install it via Chocolatey.

First, install Chocolatey:

From command line:

powershell - NoProfile - ExecutionPolicy Bypass - Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin

Then install 7-zip:

choco install 7zip.commandline
MSSQL 2016

In an ideal world, a container hosts a single application so we would have (at a minimum) one container for SQL Server, and another container for Sitecore. At the time of this writing Windows Containers do not fully support the older container linking or the newer container networking systems so for the sake of expedience I will run both inside a single container for now. Once these features are supported in Windows Containers, this will no longer be the optimal way to do things.

Create a directory to hold our files
mkdir c:\sitecore80\install\mssql
Enable .NET 3.5 framework on the host server

SQL 2016 relies on having .NET 3.5 enabled. This should be a simple dependency to meet, however as discovered by Scott McCormick containers running under the Azure image Microsoft currently provides do not come with this feature enabled and also cannot connect to Windows Update in order to install it.

Scott’s workaround is to install 3.5 on the host and import the registry entries into the container to convince the MSSQL 2016 installer that the framework is actually installed. This likely has side effects and should not be treated as a long term solution once this situation is resolved, but it works well enough for our purposes.

Run the following PowerShell commands to install the 3.5 framework on the host server:

get-windowsfeature -name NET-Framework-Features | install-windowsfeature

get-windowsfeature -name NET-Framework-Core | install-windowsfeature

Export the necessary .NET 3.5 keys to trick the container into thinking the framework is installed:

reg EXPORT "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5" c:\sitecore80\install\mssql\net35keys.reg /y
Get the MSSQL 2016 installer

SQL Server 2016 is currently in community tech preview status. Download the installer:

wget -uri 'http://care.dlservice.microsoft.com/dl/download/F/D/1/FD1242AC-06FF-4D95-A827-DE5B5978DE08/SQLServer2016-x64-ENU.exe' -outfile c:\sitecore80\install\mssql\SQLServer2016-x64-ENU.exe

wget -uri 'http://care.dlservice.microsoft.com/dl/download/F/D/1/FD1242AC-06FF-4D95-A827-DE5B5978DE08/SQLServer2016-x64-ENU.box' -outfile c:\sitecore80\install\mssql\SQLServer2016-x64-ENU.box

Run SQLServer2016-x64-ENU.exe and extract the files to C:\sitecore80\install\mssql\SQLServer2016-x64-ENU

You should be able to safely delete the box and exe file at this point. If you want to keep a copy, as a backup, move them outside of this directory structure (Ex: c:\temp) to make building the Docker image faster.

Create your unattended SQL install config file

You need to be able to work in containers from a command line, without a GUI. To do so, create a configuration file we can feed into the MSSQL 2016 installer for silent install mode. Once again, Scott has done all of the legwork for us on this one already. You can create your own or get a copy of his excellent configuration file here: http://d1kgm347lql2s5.cloudfront.net/blog/wp-content/uploads/configurationfile.txt

PS C:\sitecore80\install\mssql> wget -uri 'http://d1kgm347lql2s5.cloudfront.net/blog/wp-content/uploads/configurationfile.txt' -outfile configurationfile.ini

NOTE: The sa password set by Scott’s file is CHANGEME. You should listen and change it. This password will fail due to not meeting the default password complexity check.

If you choose to create your own config file, please note that there are a number of features not supported under Windows Server Core. Learn more here: https://msdn.microsoft.com/en-us/library/hh231669.aspx

Create the SQL installation script
PS C:\sitecore80\install\mssql> notepad install.bat

The contents of the file should read:

DISM /online /enable-feature /featurename:NetFx3ServerFeatures

reg import C:\install\mssql\net35keys.reg

c:\install\mssql\SQLServer2016-x64-ENU\setup.exe /IAcceptSQLServerLicenseTerms /ConfigurationFile=c:\install\mssql\configurationfile.ini

sc config MSSQLSERVER obj=LocalSystem

sc config MSSQLSERVER start=auto

We’ll invoke this later as part of the Docker build process.

Let’s take a look at what each of these commands does:

  • You may notice that c:\install doesn’t match anything on the server. This is a correct observation - these are the paths for the files that WILL exist in the future container.
  • DISM /online /enable-feature /featurename:NetFx3ServerFeatures
    Enables enough of the framework for the installer to run.
  • reg import C:\install\mssql\net35keys.reg
    Imports supplementary registry information (the ones we exported before) to trick the installer into successfully running. Ideally this step won’t be necessary in the future if/when we can actually enable the framework.
  • c:\install\mssql\SQLServer2016-x64-ENU\setup.exe /IAcceptSQLServerLicenseTerms /ConfigurationFile=c:\install\mssql\configurationfile.ini
    Runs the SQL Installer in unattended/silent mode with the configuration file we created earlier.
  • sc config MSSQLSERVER obj=LocalSystem
    Configures the MSSQLSERVER service to run as LocalSystem. While this may not be ideal in many circumstances, this sidesteps issues with the container not shutting down cleanly - yet another solution courtesy of Scott McCormick.
  • sc config MSSQLSERVER start=auto
    Configures the MSSQLSERVER service to automatically start. Based on our config file this shouldn’t be necessary, but in the case that the service doesn’t automatically start, this seems to correct it.

STEP 2 - Sitecore 8

Allow HTTP traffic through windows firewall

Run the following PowerShell command:

if (!(Get-NetFirewallRule | where {$_.Name -eq "TCP80"})) { New-NetFirewallRule -Name "TCP80" -DisplayName "HTTP on TCP/80" -Protocol tcp -LocalPort 80 -Action Allow -Enabled True}

Now we need to get a copy of the Sitecore web files onto our server. The end goal is to get a zip of the Sitecore web root package over to c:\sitecore80\install\sitecore\sc8.zip

Download Approach 1 - Download Sitecore PowerShell Script:

Download Approach 2 - Manual Download:

mkdir c:\sitecore80\install\sitecore\

cd c:\sitecore80\install\sitecore

wget -uri 'http://YOURSERVER.COM/Sitecore 8.0 rev. 141212.zip' -outfile 'c:\sitecore80\install\sitecore\sc8.zip'

Extract the contents:

PS C:\sitecore80\install\sitecore> 7za x .\sc8.zip

Move the files to where we want them to be:

PS C:\sitecore80\install\sitecore> move '.\Sitecore 8.0 rev. 141212\Website\' . \

PS C:\sitecore80\install\sitecore> move '.\Sitecore 8.0 rev. 141212\Data\' .\Website\

(yes, currently the /Data directory will sit in the /Website folder)

Get rid of the files we no longer need:

PS C:\sitecore80\install\sitecore> rd '.\Sitecore 8.0 rev. 141212\'

PS C:\sitecore80\install\sitecore> rm sc8.zip
Edit the config files

We need to edit the config files to values that will eventually work in the container.

Set customErrors to Off to make life easier later:

PS C:\sitecore80\install\sitecore\website> notepad web.config

<customErrors mode="Off" />

Edit the connection strings:

PS C:\sitecore80\install\sitecore\website> notepad .\app_config\ConnectionStrings.config

<add name="core" connectionString="user id=sa;password=CHANGEME;Data Source=(local);Database=Sitecore_Core" />

<add name="master" connectionString="user id=sa;password=CHANGEME;Data Source=(local);Database=Sitecore_Master" />

<add name="web" connectionString="user id=sa;password=CHANGEME;Data Source=(local);Database=Sitecore_Web" />

Next, we’ll get our hands on the databases in a way that’s easy to consume from inside the container. I opted to go with BACPAC files. You can create your own, or simply use the ones Oleg Burov (https://github.com/olegburov) maintains. These are intended to make life on SQL Azure easier, but they’ll work equally well for our purposes.

mkdir c:\sitecore80\install\sitecore\databases

wget -uri 'https://github.com/olegburov/Sitecore-Azure-Content/blob/master/articles/media/how-to-deploy-sitecore-databases-to-azure-sql-database/Sitecore80.Core.bacpac?raw=true' -outfile 'c:\sitecore80\install\sitecore\databases\sitecore80.core.bacpac'

wget -uri 'https://github.com/olegburov/Sitecore-Azure-Content/blob/master/articles/media/how-to-deploy-sitecore-databases-to-azure-sql-database/Sitecore80.Master.bacpac?raw=true' -outfile 'c:\sitecore80\install\sitecore\databases\sitecore80.master.bacpac'

wget -uri 'https://github.com/olegburov/Sitecore-Azure-Content/blob/master/articles/media/how-to-deploy-sitecore-databases-to-azure-sql-database/Sitecore80.Web.bacpac?raw=true' -outfile 'c:\sitecore80\install\sitecore\databases\sitecore80.web.bacpac'
Create the Sitecore installation script
PS C:\sitecore80\install\sitecore\scripts> notepad InstallSitecore.bat

The contents of the file should be as follows:

"c:\Program Files (x86)\Microsoft SQL Server\130\DAC\bin\SqlPackage.exe" /a:import /sf:c:\install\sitecore\databases\sitecore80.core.bacpac /tdn:Sitecore_Core /tsn:localhost

"c:\Program Files (x86)\Microsoft SQL Server\130\DAC\bin\SqlPackage.exe" /a:import /sf:c:\install\sitecore\databases\sitecore80.web.bacpac /tdn:Sitecore_Web /tsn:localhost

"c:\Program Files (x86)\Microsoft SQL Server\130\DAC\bin\SqlPackage.exe" /a:import /sf:c:\install\sitecore\databases\sitecore80.master.bacpac /tdn:Sitecore_Master /tsn:localhost

dism /online /enable-feature /all /featurename:iis-webserver /NoRestart

dism /online /enable-feature /all /featurename:IIS-ASPNET45

mkdir c:\inetpub\sitecore

XCOPY /E c:\install\Sitecore\Website c:\inetpub\Sitecore

icacls c:\inetpub\Sitecore /t /grant Everyone:F

C:\Windows\System32\inetsrv\appcmd.exe delete site "default web site"

C:\Windows\System32\inetsrv\appcmd.exe add site /name:Sitecore /bindings:http/*:80: /physicalPath:c:\inetpub\sitecore\

C:\Windows\System32\inetsrv\appcmd.exe start site Sitecore


Let’s take a look at what all of this does:

  • "c:\Program Files (x86)\Microsoft SQL Server\130\DAC\bin\SqlPackage.exe" /a:import /sf:c:\install\sitecore\databases\sitecore80.core.bacpac /tdn:Sitecore_Core /tsn:localhost

    "c:\Program Files (x86)\Microsoft SQL Server\130\DAC\bin\SqlPackage.exe" /a:import /sf:c:\install\sitecore\databases\sitecore80.web.bacpac /tdn:Sitecore_Web /tsn:localhost

    "c:\Program Files (x86)\Microsoft SQL Server\130\DAC\bin\SqlPackage.exe" /a:import /sf:c:\install\sitecore\databases\sitecore80.master.bacpac /tdn:Sitecore_Master /tsn:localhost

    These use SqlPackage to import the BACPAC files to SQL databases.
  • dism /online /enable-feature /all /featurename:iis-webserver /NoRestart
    Enables the webserver feature inside the container.
  • dism /online /enable-feature /all /featurename:IIS-ASPNET45
    Enables ASP.NET 4.5 in the container.
  • mkdir c:\inetpub\sitecore

    XCOPY /E c:\install\Sitecore\Website c:\inetpub\Sitecore

    Copies the Sitecore webroot template we created (mounted under c:\install inside the container) to c:\inetpub\sitecore.
  • icacls c:\inetpub\Sitecore /t /grant Everyone:F
    Allows ‘Everyone’ full access to c:\inetpub\sitecore (for testing purposes I feel this is fine. If you intend to do anything more in-depth with your container you may want to revisit this).
  • C:\Windows\System32\inetsrv\appcmd.exe delete site "default web site"
    Deletes the default website IIS comes with.
  • C:\Windows\System32\inetsrv\appcmd.exe add site /name:Sitecore /bindings:http/*:80: /physicalPath:c:\inetpub\sitecore\
    Creates a new site and app pool for our site.
  • C:\Windows\System32\inetsrv\appcmd.exe start site Sitecore
    Starts the site.
  • iisreset
    Restarts IIS (this doesn’t hurt and seems to help when creating new app pools and sites rapid-fire).

Create the license copying script

I am uncomfortable embedding my permanent license file inside a Docker image, so I’ve been using the following type script to copy it in from a mount point within the container host.

Create c:\sitecore80\install\sitecore\CopyLicense.bat with the following contents:

copy c:\shared\license.xml c:\inetpub\sitecore\data\license.xml:base

As an alternative, install Notepad++ and copy/paste this from your workstation.

choco install notepadplusplus

set PATH=%PATH%;C:\Program Files (x86)\Notepad++

PS C:\sitecore80\install\sitecore\Website\Data> notepad++ license.xml

If you wanted to get creative, you could have this pull from just about anywhere instead.

Although I advise against it, you could skip this step if you were to put your license.xml file in c:\sitecore80\install\sitecore\website\data prior to building the Docker image.

STEP 3 - Docker (finally!)

Create the Dockerfile

From c:\Sitecore80:

echo "" > dockerfile

notepad dockerfile


FROM windowsservercore

LABEL Description="Sitecore" Vendor="Sitecore" Version="8.0"

#Add Files to image

ADD install /install

#Install SQL 2016

RUN c:\install\mssql\install.bat

#Install Sitecore

RUN c:\install\sitecore\InstallSitecore.bat


  • FROM windowsservercore
    Sets the parent image we’re inheriting from; this is the one MS provides. You can see what else is installed on your machine with the “Docker images” command.
  • LABEL Description="Sitecore" Vendor="Sitecore" Version="8.0"
    Puts a descriptive label on our image.
  • ADD install /install
    Copies the contents of C:\Sitecore80\Install to c:\Install inside the container image.
  • RUN c:\install\mssql\install.bat
    Executes the sql installation script we created inside the container.
  • RUN c:\install\sitecore\InstallSitecore.bat
    Executes the Sitecore installation script we created inside the container.
Build the image
docker build -t sitecore80 c:\Sitecore80

This is going to take a while, seriously. Get some coffee. And then lunch. And then some more coffee.

  • The parameter to “t” needs to be lowercase.  Failure to comply results in the very helpful (eye roll) “invalid reference format” error message.
  • If the build fails for some reason (for example, you didn’t listen to me and change the sa password…) and you need to make some configuration changes, you may find the no-cache parameter handy.
Run the container
docker run -it -p 80:80 -v c:\tools:c:\shared\ sitecore80 cmd

This runs the Docker container attached to an interactive terminal running INSIDE the container. Port 80 in the container is exposed as port 80 on the host (for you to be able to verify it worked from the outside world). 

Please note that in this case I copied my Sitecore license to c:\tools\license.xml, then mounted c:\tools on the host as c:\shared inside the container. This path will be used by the license copying script. If you’ve embedded the license in the image, you can skip this step.

NOTE: Copying your license file over RDP into Notepad will typically corrupt it, and I recommend getting it into the host VM some other way.

Execute c:\sitecore80\install\sitecore\CopyLicense.bat to install the license into the Sitecore instance’s data directory.

Visit http://YOURCONTAINER.cloudapp.net to see Sitecore running in your container.

Current Limitations and Future Improvements

As of Windows Server 2016 CTP4, not every Docker feature is fully supported. There are some things I’d like to do differently in the future as support continues to improve.

  • Split SQL and Sitecore into separate coordinating containers.
  • Add a mongo container.
  • Layer/Compose images better. For example:
    • Base SQL
      • Sitecore SQL
    • Base Web
      • Sitecore Web
  • Use a data volume to put the license file into the image with fewer pain points.
  • Integrate containers with our CI process.

Resources / Credits