Building a single Docker image is straightforward, building multiple Docker images is slightly more difficult, but building multiple Docker images that depend on eachother is a daunting task. The latter is where a good build system can really help you.
The basic idea is to leverage a build system for building Docker images by creating a build target per Docker image and let the build system calculate (and execute) the dependency tree for you.
The build system I chose for this is NUKE. It's a relatively new kid on the block (2 years old) in the world of (.Net) build systems, where more commonly PSake, Cake, Fake, etc are used, but its ease-of-use is very promising and it has an active community. In short NUKE is a cross-platform build automation system with a C# DSL. NUKE basically creates a .Net Core console application that you run to perform your build. Having a C# build system seems to be a natural fit for Sitecore developers who typically write C# code.
Looking at the characteristics of a Sitecore Docker image build it consists of a number of images. As basis it has a number base images:
openjdk
: Windows Server Core + OpenJDK sitecore
: Windows Server Core + ASP.NET + commonly used tools (e.g. SIF, choco, vim)solr-build
: Solr installation (on top of openjdk
)For each Docker image we define a NUKE target, e.g. for the openjdk
image:
Target BaseOpenJdk => _ => _
.Executes(() =>
{
DockerBuild(x => x
.SetPath(".")
.SetFile("base/openjdk/Dockerfile")
.SetTag(BaseImageName("openjdk"))
);
});
In above code snippet (full snippet here) we define a target name BaseOpenJdk and it perform a Docker build command. The similar Docker CLI command would be:
PS> docker build -file "base/openjdk/Dockerfile" -tag "openjdk" .
NUKE has the DockerBuild
command built-in.
As you can see the solr-build
image depends on the openjdk
image. To specify this dependency we use the DependsOn
clause for the solr-build
target (full snippet here):
Target BaseSolrBuilder => _ => _
.DependsOn(BaseSitecore)
.Executes(() =>
{
...
DockerBuild(x => x
...
.SetBuildArg(new string[] {
$"BASE_IMAGE={baseImage}"
})
);
});
NUKE will now build the openjdk
target before the solr-builder
target which is exactly what we need. As bonus we can now easily configure what the base image of the solr-builder
is using a variable (BASE_IMAGE
) in the Dockerfile. This gives us the flexibility to manage the versioniong (using Docker tags) in NUKE instead of having the Docker tags hard-coded in (multiple) Dockerfiles.
Above approach, using DependsOn
, can be applied similarly to build the Sitecore XP Docker images (see here). Note that NUKE targets are grouped in separate C# files, each holding a partial C# class that inherits from NukeBuild
.
Things get more complicated when a running Sitecore system is required to create a Docker image, as is the case for Sitecore XC Docker images. A running Sitecore system is basically required because a Sitecore Package, e.g. Sitecore Commerce Connect, cannot be installed offline. To tackle this challenge during a NUKE build we;
For adding Commerce Connect to the XC Sitecore and MSSQL Docker image the code (full snippet here) looks as follows:
Target XcSitecoreMssql => _ => _
...
.DependsOn(XcCommerce, XcMssqlIntermediate, XcSitecoreIntermediate, XcSolr, XcXconnect)
.Executes(() => {
System.IO.Directory.SetCurrentDirectory("xc");
...
InstallSitecorePackage(
@"C:\Scripts\InstallCommercePackages.ps1",
XcImageName("sitecore"),
XcImageName("mssql"),
"-f docker-compose.yml"
);
...
});
In above code we define that it depends on XC Commerce, Mssql, Sitecore, Solr and Xconnect targets (which produce each a Docker image) before the XcSitecoreMssql
target can be executed. Note that we name the target/Docker images that are not final (and only used during build) as intermediate.
Once the pre-conditions are met we perform InstallSitecorePackage
, this performs the earlier mentioned steps:
private void InstallSitecorePackage(...)
{
DockerCompose($"{dockerComposeOptions} {DockerComposeSilenceOptions} down");
try
{
DockerCompose($"{dockerComposeOptions} {DockerComposeSilenceOptions} up -d");
// Install Commerce Connect package
var sitecoreContainerName = GetContainerName("sitecore");
DockerExec(x => x
.SetContainer(sitecoreContainerName)
.SetCommand("powershell")
.SetArgs(scriptFilename)
);
DockerCompose($"{DockerComposeSilenceOptions} stop");
// Persist changes to DB installation directory
DockerCompose($"{dockerComposeOptions} {DockerComposeSilenceOptions} up -d mssql");
...
// Commit changes
DockerCommit(x => x
.SetContainer(mssqlContainerName)
.SetRepository(mssqlTargetImageName));
DockerCommit(x => x
.SetContainer(sitecoreContainerName)
.SetRepository(sitecoreTargetImageName));
}
...
}
The full code for it can be found here
All the Sitecore Docker images together (base, XP, XC, and variants) result in a quite complex build order which isn't managable without a system, like NUKE, that calculates it for you. The current plan, without all Push targets (which are used to push the images to a Docker registry), looks as follows:
I hope this article, and the full setup available here, will help you to build your (Sitecore) Docker images and I look forward to receiving any feedback!
© Joost Meijles 2019