Build Sitecore solutions

Abstract

Explains how you build Sitecore solutions.

This topic describes the Dockerfile and the Docker image build process and how you use it to build your Sitecore solution.

If you have not already done so, clone the Docker Examples repository to a location on your machine. In this topic, we use the custom-images folder. This topic assumes that you have gone through the Getting Started documentation and that you can successfully run an out-of-the-box Sitecore instance.

This topic shows how you build container images with your solution code. To ensure efficient feedback loops during development, you must also configure your development environment for deploying files into running containers.

You write a Dockerfile as the first step in containerizing your application. A Dockerfile contains instructions that Docker uses to assemble a Docker image and run it. The Dockerfile commands are a step-by-step recipe for how to build up your image.

The following is a Dockerfile example for a simple (non-Sitecore) ASP.NET MVC app:

FROM mcr.microsoft.com/dotnet/framework/sdk:4.8 AS build
WORKDIR /app

COPY *.sln .
COPY aspnetmvcapp/*.csproj ./aspnetmvcapp/
RUN nuget restore

COPY aspnetmvcapp/. ./aspnetmvcapp/
WORKDIR /app/aspnetmvcapp
RUN msbuild /p:Configuration=Release

FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8 AS runtime
WORKDIR /inetpub/wwwroot
COPY --from=build /app/aspnetmvcapp/. ./

This example uses a multi-stage build: first the code is built with the mcr.microsoft.com/dotnet/framework/sdk image (the build stage), and then the output is copied to the mcr.microsoft.com/dotnet/framework/aspnet image (the runtime stage).

The Dockerfile is then used by the Docker build command and this command builds and creates the image.

An optional .dockerignore can be used to exclude files and folders from the build context, reducing the size or skipping sensitive data in COPY and ADD commands.

The example shows that code compilation and build are part of a Dockerfile's instructions. In .NET, this could include a NuGet restore, followed by invoking MSBuild. Building your application directly in a Dockerfile like this has advantages over a more traditional build, including:

  • Portability - The entire build process is containerized. Build agents and local environments do not need to have anything installed other than Docker.

  • Control over the build environment - The solution owns which build dependencies (and which versions) are used.

  • Efficiency - Even if build steps are very verbose, use of the Docker build cache optimizes rebuilds.

  • Plays nice in Docker ecosystem - Builds can be triggered using Docker commands and retrieving build artifacts for use in other images is simple with Dockerfile FROM instructions.

Note

This topic focuses on using a Dockerfile. Although building in a Dockerfile is preferred, but you might have to rely on traditional means to build your solution (for example because of the limitations of a legacy codebase or build process).

With a typical Sitecore implementation, a single Visual Studio solution usually creates build artifacts for multiple roles, for example, Website and XConnect assemblies, and some artifacts must be deployed to multiple roles (for example CM/CD). For containers, this means that the same build output must be layered on top of multiple base Sitecore runtime images. You could instead duplicate the build instructions for each Sitecore image, but this would be very inefficient.

What you need is a Dockerfile that is focused solely on building your solution and storing the output as structured build artifacts on the resulting image.

Build Dockerfile

The root Dockerfile in the example is such a Dockerfile. The Docker community often name this type of build Dockerfile Dockerfile.build.

Navigate to the custom-images folder, and look at the Dockerfile located there. Note that for simplicity, the BASE_IMAGE and BUILD_IMAGE ARGs have been expanded:

  1. The file starts with an escape directive to set the escape character to a backtick (`), instead of the default backslash:

    # escape=`
  2. A prep stage is used to gather only artifacts necessary for NuGet restore. This is a common optimization made in .NET builds (see Dockerfile best practices for an explanation):

    FROM mcr.microsoft.com/dotnet/framework/sdk:4.8 AS prep
    
    COPY *.sln nuget.config Directory.Build.targets Packages.props \nuget\
    COPY src\ \temp\
    RUN Invoke-Expression 'robocopy C:\temp C:\nuget\src /s /ndl /njh /njs *.csproj *.scproj packages.config'
  3. A new builder stage initiates the code compilation and build process, based on the .NET Framework SDK image, and a BUILD_CONFIGURATION ARG is declared (this is either debug or release, configured in Docker Compose):

    FROM mcr.microsoft.com/dotnet/framework/sdk:4.8 AS builder
    ARG BUILD_CONFIGURATION
  4. The SHELL instruction switches the default shell to PowerShell (from the default of cmd) for all subsequent instructions. This is a common instruction in Windows Dockerfiles:

    SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
  5. Next, a working directory is created, the NuGet artifacts collected earlier are copied in, and a (optimized) nuget restore is performed:

    WORKDIR C:\build
    COPY --from=prep .\nuget .\
    RUN nuget restore
  6. After the restore, the remainder of the source code is copied in, and transform files are collected at C:\out\transforms:

    COPY src\ .\src\
    RUN Invoke-Expression 'robocopy C:\build\src C:\out\transforms /s /ndl /njh /njs *.xdt'

    The example includes a .dockerignore (located alongside the Dockerfile). This reduces the size of the COPY commands and excludes things like bin and obj folders. This is the best practice for build Dockerfiles. For more information about config transforms, see Applying configuration transforms.

  7. Next, the build is performed for the configured BUILD_CONFIGURATION using msbuild. The example contains projects targeting both the website/platform (DockerExamples.Website.csproj) and xConnect (DockerExamples.XConnect.csproj) environments. A simple file system publish is used, with the output landing at C:\out\website and C:\out\xconnect respectively:

    RUN msbuild .\src\DockerExamples.Website\DockerExamples.Website.csproj /p:Configuration=$env:BUILD_CONFIGURATION /p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:PublishUrl=C:\out\website
    RUN msbuild .\src\DockerExamples.XConnect\DockerExamples.XConnect.csproj /p:Configuration=$env:BUILD_CONFIGURATION /p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:PublishUrl=C:\out\xconnect
  8. After the build is completed, the build output for the final image is collected. For this stage, the Microsoft Nano Server image is used. Because this image only delivers files and is never run, the base image is selected for its optimized size - much smaller than other runtime images:

    FROM mcr.microsoft.com/windows/nanoserver:1809
  9. The output files are copied in from the builder stage to our final image with the following structure:

    • \artifacts\website

    • \artifacts\transforms

    • \artifacts\xconnect

    WORKDIR C:\artifacts
    COPY --from=builder C:\out\website .\website\
    COPY --from=builder C:\out\transforms .\transforms\
    COPY --from=builder C:\out\xconnect .\xconnect\

Note

This example does not do Sitecore item serialization. Depending on your serialization framework and strategy, your solution build Dockerfile can have additional instructions. See Deploying items for details.

Solution image

The solution build Dockerfile produces an image that consists only of build artifacts. The resulting solution image is never intended to be run in a Docker container. Such images are sometimes called asset images.

You must send a Dockerfile through the Docker build command to produce an image. You can certainly use build command directly, but it is mostly configured with Docker Compose.

The custom-images folder contains the following files:

  • .env

  • docker-compose.yml

  • docker-compose.override.yml

These are all types of Docker Compose files.

Understand docker-compose.override.yml

For any Docker Compose command (such as docker-compose up -d), the docker-compose.override.yml file is automatically included along with your main docker-compose.yml file, unless you specify otherwise. When Docker Compose is handed two or more compose files, it merges them together, bringing all the resources and configuration together into a single merged definition.

In this example, the docker-compose.yml file is the default Sitecore Experience Platform - Single (XP0) Docker Compose file that comes from Sitecore. The docker-compose.override.yml extends the main file with overrides and extensions necessary for custom Sitecore image build and development purposes.

Configure the solution service

The docker-compose.override.yml file is where you define the solution image build.

Open the file and look at the solution service to see how it is configured:

solution:
  image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-solution:${VERSION:-latest}
  build:
    context: .
    args:
      BASE_IMAGE: ${SOLUTION_BASE_IMAGE}
      BUILD_IMAGE: ${SOLUTION_BUILD_IMAGE}
      BUILD_CONFIGURATION: ${BUILD_CONFIGURATION}
  scale: 0

Note the following points:

  • Variable values (for example, ${SOLUTION_BASE_IMAGE}) are defined in the environment file (.env) in this example, but can also be sourced from system environment variables on local development machines or secrets on your build server.

  • The image name uses a -solution suffix. With the default variable values, the tagged version is docker-examples-solution:latest

  • The build context is set to . to tell Docker Compose to use the build Dockerfile at the same location.

  • The build args correlate to those found in the build Dockerfile.

  • scale is set to 0 so that a container for the solution service is not started during a docker-compose up.

To build the solution image:

  1. Open a PowerShell prompt and run the following from the same folder as your Compose files:

    docker-compose build solution

    This initiates the build process for the solution image and creates your image:

    Building solution
    Step 1/21 : ARG BASE_IMAGE
    Step 2/21 : ARG BUILD_IMAGE
    Step 3/21 : FROM ${BUILD_IMAGE} AS prep
    [...]
    Successfully built 9bb20b2ab6db
    Successfully tagged docker-examples-solution:latest
  2. Confirm the image was created by listing all Docker images:

    docker images
    REPOSITORY                TAG     IMAGE ID      CREATED        SIZE
    docker-examples-solution  latest  9bb20b2ab6db  2 minutes ago  259MB

Start by looking at the troubleshooting guide.

One common troubleshooting problem unique to the solution image (or any asset image) is that because it is never run as a container, it might not be obvious how you can explore the resulting file system. You can do this by running the image with an interactive shell.

For example, to open up an interactive command prompt (Nano Server does not have PowerShell) to the solution image example given previously:

docker run -it --rm docker-examples-solution:latest

Type exit to remove the temporary container and return to your previous PowerShell session.