Sitecore 10 with Docker – Create a solution from the scratch – Setup Sitecore 10.3 XM setup with custom images

  1. Sitecore 10 with Docker – Create a solution from the scratch – Setting up the first docker instance
  2. Sitecore 10 with Docker – Create a solution from the scratch – Setup Sitecore 10.3 XM setup with custom images
  3. Sitecore 10 with Docker – Create a solution from the scratch – Add Modules and enable Sitecore Serialization
  4. Sitecore 10 with Docker – Create a solution from the scratch – Create solution and enable Sitecore Serialization
  5. Sitecore 10 with Docker – Create a solution from the scratch – Add ASP.NET Rendering Host and configure SXA-JSS

Change folder structure and configure basic Sitecore 10.3 XM setup with custom images

In our first part we spined up the XP0 environment provided by the getting started template.

Now we will configure docker, to use another folder structure and to spin up Sitecore XM.

Let’s first have a look on the .env file.

The .env file provides variables to the docker compose file when calling docker compose up -d

In it’s raw state the file looks as following:

Most of the values are self explaining. Just have a look here if you want to know more on how to set the necessary values.

After running the Init command in PowerShell it will look similar to

So the variables are prefilled to start docker compose.

We will extend and modify the .env and docker-compose.yml file and use a docker-compose.override.yml file to override the contents in docker-compose.yml

When looking into the custom-images folder of the docker-examples we see a different file and folder structure

Ignore for now the different docker-compose files as they are just for different topologies. . When comparing it to the Helix-Examples it looked pretty similar, why we are now following the same approach.

Change the folder structure

We start by creating a docker folder and moved the other folders into it

In the docker folder we rename mssql-data and solr-data to mssql & solr and move it into a separate data folder

We change the code in the clean.ps1 in the root folder to following, to match our new folder structure

# Clean data folders
Get-ChildItem -Path (Join-Path $PSScriptRoot "docker\data\mssql") -Exclude ".gitkeep" -Recurse | Remove-Item -Force -Recurse -Verbose
Get-ChildItem -Path (Join-Path $PSScriptRoot "docker\data\solr") -Exclude ".gitkeep" -Recurse | Remove-Item -Force -Recurse -Verbose

We change the code in Init.ps1 in the root folder to following, to match our new folder structure

Push-Location docker\traefik\certs

Let’s add some additional folders cm &cd to our data folder, which we will mount later for our log files

Next we add a new parameters to my .env file in the root folder, which we will use later as variable in our .yml files

LOCAL_DATA_PATH=.\docker\data

EDIT THE .ENV FILE

We have already added a new entry to our .env file.

We will do now some adjustments to our .env file to work in the further steps with the different variables.

#Isolation
ISOLATION=default
TRAEFIK_ISOLATION=hyperv
#images
TRAEFIK_IMAGE=traefik:v2.2.0-windowsservercore-1809
NETCORE_BUILD_IMAGE=mcr.microsoft.com/dotnet/sdk:6.0
SOLUTION_BUILD_IMAGE=mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2019
SOLUTION_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:1809
#hosts
CM_HOST=cm.mydockerexperience.localhost
CD_HOST=cd.mydockerexperience.localhost
ID_HOST=id.mydockerexperience.localhost
RENDERING_HOST=www.mydockerexperience.localhost
#registry
SITECORE_DOCKER_REGISTRY=scr.sitecore.com/sxp/
SITECORE_TOOLS_REGISTRY=scr.sitecore.com/tools/
SITECORE_MODULE_REGISTRY=scr.sitecore.com/sxp/modules/
#build
BUILD_CONFIGURATION=debug
#Sitecore config
COMPOSE_PROJECT_NAME=sitecore-xm1
TOOLS_VERSION=10.2-1809
SITECORE_VERSION=10.3-ltsc2019
HOST_LICENSE_FOLDER=C:\Sitecore\License
JSS_EDITING_SECRET=8E1FE24B-17B2-4AB3-B697-F191C408305A
SOLR_CORE_PREFIX_NAME=sitecore
#SC Modules
SPE_VERSION=6.4-1809
SXA_VERSION=10.3-1809
HEADLESS_SERVICES_VERSION=21.0-1809
MANAGEMENT_SERVICES_VERSION=5.1.25-1809
#Passwords
SITECORE_ADMIN_PASSWORD=Password12345
SQL_SA_PASSWORD=rM3Eow9jNrzlcuqs1X5
SQL_SERVER =mssql
SQL_SA_LOGIN=sa
SQL_DATABASE_PREFIX=Sitecore
#Path configuration
LOCAL_DEPLOY_PATH=.\docker\deploy
LOCAL_DATA_PATH=.\docker\data
EXTERNAL_IMAGE_TAG_SUFFIX=ltsc2019
#Sitecore id 
SITECORE_ID_CERTIFICATE<SITECORE_ID_CERTIFICATE>
SITECORE_ID_CERTIFICATE_PASSWORD=<SITECORE_ID_CERTIFICATE_PASSWORD>
# You should change the shared secret to a random string and not use the default value
MEDIA_REQUEST_PROTECTION_SHARED_SECRET=>sNDw]9}hB~Z2SCwK1jtYUgY?5P2I-/)w]Jok3MTFez*xV*CWqBGG=j7&hn//<xi
##additional configuration
TELERIK_ENCRYPTION_KEY=<TELERIK_ENCRYPTION_KEY>
SITECORE_IDSECRET=<SITECORE_IDSECRET>
SITECORE_LICENSE=<SITECORE_LICENSE_HASH>

Renew initialization

Let’s have a look what we have done in the previous step. (Let’s skip for now the things which need no explanation or change)

We changed our Host names and added a new one for our rendering host

#hosts
CM_HOST=cm.mydockerexperience.localhost
CD_HOST=cd.mydockerexperience.localhost
ID_HOST=id.mydockerexperience.localhost
RENDERING_HOST=www.mydockerexperience.localhost

Here we need to do some adjustments to our current files.

We open .\docker\traefik\config\certs_config.yaml

We change the content from

tls:
  certificates:
    - certFile: C:\etc\traefik\certs\xp0cm.localhost.crt
      keyFile: C:\etc\traefik\certs\xp0cm.localhost.key
    - certFile: C:\etc\traefik\certs\xp0id.localhost.crt
      keyFile: C:\etc\traefik\certs\xp0id.localhost.key

to

tls:
  certificates:
    - certFile: C:\etc\traefik\certs\_wildcard.mydockerexperience.localhost.pem
      keyFile: C:\etc\traefik\certs\_wildcard.mydockerexperience.localhost-key.pem

In the root folder we create a docker-compose.override.yml file and added following

What we did is telling our traefik container to consume a wildcard certificate from C:\etc\traefik\certs_wildcard.mydockerexperience.localhost.pem.

BUT wait!!! We have not yet created the certificate.

Let’s go back to our Init.ps1 in our root folder and search for following entry

 Write-Host "Generating Traefik TLS certificates..." -ForegroundColor Green
    & $mkcert -install
    & $mkcert -cert-file xp0cm.localhost.crt -key-file xp0cm.localhost.key "xp0cm.localhost"
    & $mkcert -cert-file xp0cd.localhost.crt -key-file xp0cd.localhost.key "xp0cd.localhost"
    & $mkcert -cert-file xp0id.localhost.crt -key-file xp0id.localhost.key "xp0id.localhost"

Let’s replace this by

    Write-Host "Generating Traefik TLS certificates..." -ForegroundColor Green
    & $mkcert -install
    & $mkcert "*.mydockerexperience.localhost"

Add the bottom of the Init.ps1 file we find following

Write-Host "Adding Windows hosts file entries..." -ForegroundColor Green

Add-HostsEntry "xp0cm.localhost"
Add-HostsEntry "xp0id.localhost"
Add-HostsEntry "xp0cd.localhost"

We replace it with our new hosts

Write-Host "Adding Windows hosts file entries..." -ForegroundColor Green

Add-HostsEntry "cm.mydockerexperience.localhost"
Add-HostsEntry "id.mydockerexperience.localhost"
Add-HostsEntry "cd.mydockerexperience.localhost"
Add-HostsEntry "www.mydockerexperience.localhost"

After that we can call the Init.ps1 in PowerShell again to add the new host entries and to create the new certificate.

.\init.ps1 -LicenseXmlPath "<your licensepath here>"

Configure docker-compose & docker-compose.override.yml

In the root folder we open the docker-compose.yml file and clean the whole content, beside the following:

version: "2.4"
services:

We will now configure step by step the different instances for our docker file.

Traefik

  traefik:
    isolation: ${TRAEFIK_ISOLATION}
    image: ${TRAEFIK_IMAGE}
    command:
      - "--ping"
      - "--api.insecure=true"
      - "--providers.docker.endpoint=npipe:////./pipe/docker_engine"
      - "--providers.docker.exposedByDefault=false"
      - "--providers.file.directory=C:/etc/traefik/config/dynamic"
      - "--entryPoints.websecure.address=:443"
    ports:
      - "443:443"
      - "8079:8080"
    healthcheck:
      test: ["CMD", "traefik", "healthcheck", "--ping"]
    volumes:
      - source: \\.\pipe\docker_engine
        target: \\.\pipe\docker_engine
        type: npipe
      - ./traefik:C:/etc/traefik
    depends_on:
      id:
        condition: service_healthy
      cd:
        condition: service_healthy
      cm:
        condition: service_healthy

The depends_on configuration is for later usage where we control how docker will start and when the added service is ready(In this case id,cm,cd).

We are using 2 variables

 isolation: ${TRAEFIK_ISOLATION}
 image: ${TRAEFIK_IMAGE}

which we can find in our .env file in the root folder

Notice:

volumes:
      - source: \\.\pipe\docker_engine
        target: \\.\pipe\docker_engine
        type: npipe
      - ./traefik:C:/etc/traefik

This mounts the traefic folder from our root ./traefik to C:/etc/trafic in our docker image. As we have changed the folder structure we need to override this value, to point to our new structure. Therefore we create a docker-compose.override.yml in our root folder.

The docker-compose.override.yml overrides configuration done in the docker-compose.yml.

In this file we add following

version: "2.4"

services:
  traefik:
    volumes:
      - ./docker/traefik:C:/etc/traefik
    depends_on:
      - rendering

We have now overridden the docker-compose.yml entry of trafic to mount our new folder structure.

 volumes:
      - ./docker/traefik:C:/etc/traefik

Remember we configured this path for the certificate used by the traefik container.

We will now configure our images to spin up xm by adding the necessary configuration to docker-compose & docker-compose.override

Redis

docker-compose.yml

redis:
    isolation: ${ISOLATION}
    image: ${SITECORE_DOCKER_REGISTRY}redis:3.2.100-${EXTERNAL_IMAGE_TAG_SUFFIX}
  mssql:
    isolation: ${ISOLATION}
    image: ${SITECORE_DOCKER_REGISTRY}nonproduction/mssql-developer:2017-${EXTERNAL_IMAGE_TAG_SUFFIX}
    environment:
      SA_PASSWORD: ${SQL_SA_PASSWORD}
      SITECORE_ADMIN_PASSWORD: ${SITECORE_ADMIN_PASSWORD}
      ACCEPT_EULA: "Y"
    ports:
      - "14330:1433"
    volumes:
      - type: bind
        source: .\mssql-data
        target: c:\data

We are using the previously created parameters in this configuration. We are using nonproduction/mssql-developer:2017-${EXTERNAL_IMAGE_TAG_SUFFIX} just because we are creating a XM 10.3 environment and at this point in time there are no production images available in Sitecore Image repository.

docker-compose.override.yml

  redis:
    image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-redis:${VERSION:-latest}
    build:
      context: ./docker/build/redis
      args:
        BASE_IMAGE: ${SITECORE_DOCKER_REGISTRY}redis:3.2.100-${EXTERNAL_IMAGE_TAG_SUFFIX}

Here is something new!

 image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-redis:${VERSION:-latest}

With this entry we are defining a name for a new custom image for docker

The name will be something like sitecore-xm1-redis:latest

    build:
      context: ./docker/build/redis

Here we are pointing to a subfolder of our docker folder.

This folder does not exist yet, so let’s create it

In this folder we add a DockerFile, which will be using the args parameters and as a basis for our custom images.

args:
        BASE_IMAGE: ${SITECORE_DOCKER_REGISTRY}redis:3.2.100-${EXTERNAL_IMAGE_TAG_SUFFIX}

The DockerFile in this case is very simple. It uses the BASE_IMAGE to build a custom image

# escape=`

ARG BASE_IMAGE

FROM ${BASE_IMAGE}

For now we will create the folders cd,cm,dotnetsdk,id,mssql-init,redis,rendering and solr-init and place a DockerFile with the same content in it.

These will be the custom images which we will create in the next steps.

MSSQL

docker-compose.yml

mssql:
    isolation: ${ISOLATION}
    image: ${SITECORE_DOCKER_REGISTRY}nonproduction/mssql-developer:2017-${EXTERNAL_IMAGE_TAG_SUFFIX}
    environment:
      SA_PASSWORD: ${SQL_SA_PASSWORD}
      SITECORE_ADMIN_PASSWORD: ${SITECORE_ADMIN_PASSWORD}
      ACCEPT_EULA: "Y"
    ports:
      - "14330:1433"
    volumes:
      - type: bind
        source: .\mssql-data
        target: c:\data

docker-compose.override.yml

  mssql:
    mem_limit: 2GB
    volumes:
      - ${LOCAL_DATA_PATH}\mssql:c:\data

Solr

docker-compose.yml

 solr:
    isolation: ${ISOLATION}
    image: ${SITECORE_DOCKER_REGISTRY}nonproduction/solr:8.11.2-${EXTERNAL_IMAGE_TAG_SUFFIX}
    ports:
      - "8984:8983"
    volumes:
      - type: bind
        source: .\solr-data
        target: c:\data
    environment:
      SOLR_MODE: solrcloud
    healthcheck:
      test: ["CMD", "powershell", "-command", "try { $$statusCode = (iwr http://solr:8983/solr/admin/cores?action=STATUS -UseBasicParsing).StatusCode; if ($$statusCode -eq 200) { exit 0 } else { exit 1} } catch { exit 1 }"]

docker-compose.override.yml

  # Mount our Solr data folder.
  solr:
    volumes:
      - ${LOCAL_DATA_PATH}\solr:c:\data

solr-init

docker-compose.yml

  solr-init:
    isolation: ${ISOLATION}
    image: ${SITECORE_DOCKER_REGISTRY}sitecore-xm1-solr-init:${SITECORE_VERSION}
    environment:
      SITECORE_SOLR_CONNECTION_STRING: http://solr:8983/solr
      SOLR_CORE_PREFIX_NAME: ${SOLR_CORE_PREFIX_NAME}
    depends_on:
      solr:
        condition: service_healthy

docker-compose.override.yml

  # Mount our Solr data folder and use our retagged Solr image.
  # Some modules (like SXA) also require additions to the Solr image.
  solr-init:
    image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-xm1-solr-init:${VERSION:-latest}
    build:
      context: ./docker/build/solr-init
      args:
        BASE_IMAGE: ${SITECORE_DOCKER_REGISTRY}sitecore-xm1-solr-init:${SITECORE_VERSION}

Identityserver

docker-compose.yml

  id:
    isolation: ${ISOLATION}
    image: ${SITECORE_DOCKER_REGISTRY}sitecore-id7:${SITECORE_VERSION}
    environment:
      Sitecore_Sitecore__IdentityServer__SitecoreMemberShipOptions__ConnectionString: Data Source=mssql;Initial Catalog=Sitecore.Core;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_Sitecore__IdentityServer__AccountOptions__PasswordRecoveryUrl: https://${CM_HOST}/sitecore/login?rc=1
      Sitecore_Sitecore__IdentityServer__Clients__PasswordClient__ClientSecrets__ClientSecret1: ${SITECORE_IDSECRET}
      Sitecore_Sitecore__IdentityServer__Clients__DefaultClient__AllowedCorsOrigins__AllowedCorsOriginsGroup1: https://${CM_HOST}
      Sitecore_Sitecore__IdentityServer__CertificateRawData: ${SITECORE_ID_CERTIFICATE}
      Sitecore_Sitecore__IdentityServer__PublicOrigin: https://${ID_HOST}
      Sitecore_Sitecore__IdentityServer__CertificateRawDataPassword: ${SITECORE_ID_CERTIFICATE_PASSWORD}
      Sitecore_License: ${SITECORE_LICENSE}
    healthcheck:
      test: ["CMD", "pwsh", "-command", "C:/Healthchecks/Healthcheck.ps1"]
      timeout: 300s
    depends_on:
      mssql:
        condition: service_healthy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.id-secure.entrypoints=websecure"
      - "traefik.http.routers.id-secure.rule=Host(`${ID_HOST}`)"
      - "traefik.http.routers.id-secure.tls=true"

docker-compose.override.yml

  # Use our retagged Identity Server image.
  # Configure for a mounted license file instead of using SITECORE_LICENSE.
  id:
    image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-id7:${VERSION:-latest}
    build:
      context: ./docker/build/id
      args:
        BASE_IMAGE: ${SITECORE_DOCKER_REGISTRY}sitecore-id7:${SITECORE_VERSION}
    volumes:
      - ${HOST_LICENSE_FOLDER}:c:\license
    environment:
      SITECORE_LICENSE_LOCATION: c:\license\license.xml

Content delivery

docker-compose.yml

cd:
    isolation: ${ISOLATION}
    image: ${SITECORE_DOCKER_REGISTRY}sitecore-xm1-cd:${SITECORE_VERSION}
    depends_on:
      mssql:
        condition: service_healthy
      solr-init:
        condition: service_started
      redis:
        condition: service_started
    environment:
      Sitecore_AppSettings_instanceNameMode:define: default
      Sitecore_ConnectionStrings_Security: Data Source=mssql;Initial Catalog=Sitecore.Core;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_ConnectionStrings_Web: Data Source=mssql;Initial Catalog=Sitecore.Web;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_ConnectionStrings_ExperienceForms: Data Source=mssql;Initial Catalog=Sitecore.ExperienceForms;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_ConnectionStrings_Solr.Search: http://solr:8983/solr;solrCloud=true
      Sitecore_ConnectionStrings_Redis.Sessions: redis:6379,ssl=False,abortConnect=False
      Sitecore_License: ${SITECORE_LICENSE}
      SOLR_CORE_PREFIX_NAME: ${SOLR_CORE_PREFIX_NAME}
      MEDIA_REQUEST_PROTECTION_SHARED_SECRET: ${MEDIA_REQUEST_PROTECTION_SHARED_SECRET}
    healthcheck:
      test: ["CMD", "powershell", "-command", "C:/Healthchecks/Healthcheck.ps1"]
      timeout: 300s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.cd-secure.entrypoints=websecure"
      - "traefik.http.routers.cd-secure.rule=Host(`${CD_HOST}`)"
      - "traefik.http.routers.cd-secure.tls=true"

docker-compose.override.yml

 cd:
    image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-xm1-cd:${VERSION:-latest}
    build:
      context: ./docker/build/cd
      args:
        BASE_IMAGE: ${SITECORE_DOCKER_REGISTRY}sitecore-xm1-cd:${SITECORE_VERSION}
    depends_on:
      - solution
    volumes:
      - ${LOCAL_DATA_PATH}\cd:C:\inetpub\wwwroot\App_Data\logs

Notice: That we mounted the sitecore logs to our data folder

Content management

docker-compose.yml

 cm:
    isolation: ${ISOLATION}
    image: ${SITECORE_DOCKER_REGISTRY}sitecore-xm1-cm:${SITECORE_VERSION}
    depends_on:
      mssql:
        condition: service_healthy
      solr-init:
        condition: service_started
      id:
        condition: service_started
    environment:
      Sitecore_AppSettings_instanceNameMode:define: default
      Sitecore_ConnectionStrings_Core: Data Source=mssql;Initial Catalog=Sitecore.Core;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_ConnectionStrings_Security: Data Source=mssql;Initial Catalog=Sitecore.Core;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_ConnectionStrings_Master: Data Source=mssql;Initial Catalog=Sitecore.Master;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_ConnectionStrings_Web: Data Source=mssql;Initial Catalog=Sitecore.Web;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_ConnectionStrings_ExperienceForms: Data Source=mssql;Initial Catalog=Sitecore.ExperienceForms;User ID=sa;Password=${SQL_SA_PASSWORD}
      Sitecore_ConnectionStrings_Solr.Search: http://solr:8983/solr;solrCloud=true
      Sitecore_ConnectionStrings_Sitecoreidentity.secret: ${SITECORE_IDSECRET}
      Sitecore_AppSettings_Telerik.AsyncUpload.ConfigurationEncryptionKey: ${TELERIK_ENCRYPTION_KEY}
      Sitecore_AppSettings_Telerik.Upload.ConfigurationHashKey: ${TELERIK_ENCRYPTION_KEY}
      Sitecore_AppSettings_Telerik.Web.UI.DialogParametersEncryptionKey: ${TELERIK_ENCRYPTION_KEY}
      Sitecore_License: ${SITECORE_LICENSE}
      Sitecore_Identity_Server_Authority: https://${ID_HOST}
      Sitecore_Identity_Server_InternalAuthority: http://id
      Sitecore_Identity_Server_CallbackAuthority: https://${CM_HOST}
      Sitecore_Identity_Server_Require_Https: "false"
      SOLR_CORE_PREFIX_NAME: ${SOLR_CORE_PREFIX_NAME}
      MEDIA_REQUEST_PROTECTION_SHARED_SECRET: ${MEDIA_REQUEST_PROTECTION_SHARED_SECRET}
    healthcheck:
      test: ["CMD", "powershell", "-command", "C:/Healthchecks/Healthcheck.ps1"]
      timeout: 300s
    labels:
      - "traefik.enable=true"
      - "traefik.http.middlewares.force-STS-Header.headers.forceSTSHeader=true"
      - "traefik.http.middlewares.force-STS-Header.headers.stsSeconds=31536000"
      - "traefik.http.routers.cm-secure.entrypoints=websecure"
      - "traefik.http.routers.cm-secure.rule=Host(`${CM_HOST}`)"
      - "traefik.http.routers.cm-secure.tls=true"
      - "traefik.http.routers.cm-secure.middlewares=force-STS-Header"

docker-compose.override.yml

cm:
    image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-xm1-cm:${VERSION:-latest}
    build:
      context: ./docker/build/cm
      args:
        BASE_IMAGE: ${SITECORE_DOCKER_REGISTRY}sitecore-xm1-cm:${SITECORE_VERSION}
    depends_on:
      - solution
    volumes:
      - ${LOCAL_DATA_PATH}\cm:C:\inetpub\wwwroot\App_Data\logs

Notice: That we mounted the Sitecore logs to our data folder

If we call

docker-compose build
docker-compose up -d

from PowerShell our custom images will build and spin up as Sitecore XM, but it will be still a Vanilla Instance without any modules installed.

In the next part we are going to add Sitecore modules and Sitecore Serialization to our instance

Leave a Comment

Your email address will not be published. Required fields are marked *