
Building and deploying Sitecore solutions can be time-consuming if pipelines aren’t optimized. This article shares practical techniques we used to speed up CI/CD workflows for Sitecore XP (Kubernetes in our case) deployments, enabling faster and more efficient delivery.
In short
If you don’t want to read all the details, here are the main principles that can be applied to any CI/CD pipeline:
- Do only what’s necessary — remove redundant tasks.
- Use caching (
node_modules
, NuGet, etc.). - Reduce I/O load (e.g., file copies) — minimize the number of file transfers.
But… if you’re still interested in the details, read on.
Note: All implementation details here are for Azure DevOps pipelines, but the same principles apply to any CI/CD platform.
Run tasks conditionally
Our build pipeline is shared between regular builds (triggered on push) and deployment builds (used when deploying to an environment and generating deployment artifacts).
Note: We distinguish between those builds using the manual_release
parameter. When it’s true
(or checked), it triggers the next steps — image creation and Kubernetes deployment.
The simplest approach is to run certain steps conditionally:
- Copy deployment-only files: e.g., Unicorn serialization files or configuration transforms.
- Create and publish artifacts:
ArchiveFiles@2
,PublishBuildArtifacts@1
- Publish debug symbols:
PublishSymbols@2
Example:
- task: ArchiveFiles@2
displayName: Zip Artifact Website
...
condition: eq(variables.buildMode, 'Full') # execute this task only if buildMode is Full
Note: This optimization improves the average build performance by skipping unnecessary tasks during most builds (though it doesn’t affect worst-case build times).
Result: around 3–4 minutes faster per push-triggered build.
Caching
Caching is one of the most effective optimization techniques. With proper configuration, it can significantly improve performance.
Pipeline caching
Pipeline caching in Azure DevOps can be implemented using the Cache@2 task.
Useful cache targets include:
node_modules
— if the cache restores successfully, there’s no need to reinstall packages.- The
packages
folder for NuGet —nuget restore
still runs, but much faster.
Example:
- task: Cache@2
displayName: node_modules Cache
inputs:
key: 'node_modules | "$(Agent.OS)" | frontend/package-lock.json'
path: "frontend/node_modules"
cacheHitVar: NODE_MODULES_CACHE_RESTORED
- task: Npm@1
displayName: npm ci
inputs:
command: "ci"
workingDir: "frontend"
condition: ne(variables.NODE_MODULES_CACHE_RESTORED, 'true')
# just cache and restore Nuget packages fodler
- task: Cache@2
inputs:
key: 'nuget | "$(Agent.OS)" | Packages.props | nuget.config'
path: packages
displayName: Cache NuGet packages
Explanation: The cacheHitVar
input sets NODE_MODULES_CACHE_RESTORED
to true
if the cache was restored.
Then the npm ci
task only runs when the cache wasn’t restored — meaning no redundant package installation.
Frontend caching
Use filesystem caching to speed up continuous builds. In our case, we configured Webpack build cache.
const config = {
...,
cache: {
type: 'filesystem',
name: '[PROJECT_NAME]',
cacheDirectory: resolve(__dirname, 'node_modules', '.cache'),
buildDependencies: {
config: [__filename],
},
},
};
Note: The cache directory is stored inside node_modules
, which means when the node_modules
folder is restored from pipeline cache, Webpack can reuse its stored cache — resulting in faster local and CI/CD builds.
Result: Frontend build time (npm install
+ build) reduced from an average of 10 minutes to 3 minutes.
IO Operations
Our CI/CD setup uses a pipeline chain consisting of several stages:
Build → Solution Image → CM/CD Builds.
During this process, Unicorn serialization items must be included in the Docker images. This introduces a heavy I/O load, as a large number of .yaml
files are copied between stages.
The flow looks like this:
# | Step | Action |
---|---|---|
1 | Build | Create an items artifact that contains all serialized Unicorn items. |
2 | Solution Image | 1. Extract the artifact. 2. Copy all .yaml files into the Docker image. |
3 | CM & CD Builds | Copy the .yaml files from the Solution image into the final CM and CD images. |
Each stage involves file extraction and copying, leading to repeated I/O operations and slower overall build performance — especially when dealing with large numbers of serialized items.
Previously, it looked like this:
Pipeline:
- task: ExtractFiles@1
displayName: Extract Artifact | Website
inputs:
archiveFilePatterns: $(Pipeline.Workspace)\build\$(artifact_prefix)_Website\*.zip
destinationFolder: $(Build.SourcesDirectory)\backend\docker\build\solution.prebuild\artifacts\website
Solution Dockerfile — copying items from the extracted artifact into the image.
...
WORKDIR c:\prebuild
COPY artifacts\ .
RUN `
Invoke-Expression 'robocopy C:\prebuild\website\App_Data\unicorn\src C:\out\items /s /ndl /nfl /njh /njs /nooffload /np *.yml'; `
...
CM Dockerfile — copying from the prebuild solution image into the CM image.
...
# Copy serialized items and Unicorn sync script
COPY --from=solution \artifacts\items\ \items\
...
This means there were a lot of copy operations.
The image build took up to 20 minutes due to the heavy I/O load from copying thousands of files.
The solution was to optimize it.
After optimization, it now looks like this:
- task: CopyFiles@2
displayName: Copy Artifact | Website
inputs:
SourceFolder: $(Pipeline.Workspace)\build\$(artifact_prefix)_Website
Contents: '*.zip'
TargetFolder: $(Build.SourcesDirectory)\backend\docker\build\solution.prebuild\artifacts\website
Solution Dockerfile — just copy the zip archive into the Docker image.
...
WORKDIR c:\archives
COPY artifacts .
...
CM Dockerfile — keep the old copying for compatibility with local builds, but extract the zip if it’s a CI/CD build.
# Copy serialized items and Unicorn sync script
COPY --from=solution \artifacts\items\ \items\
# Extract archives
RUN if (Test-Path C:\archives\items.zip) { Invoke-Expression 'tar -xf C:\archives\items.zip -C C:\items' }
Result:
- Solution image build time reduced from 20 minutes to 1 minute.
- CM image build time reduced from 20 minutes to 10 minutes.
Conclusion
I hope the ideas described here help you optimize your CI/CD pipelines and reduce build times.