This post is the second blog post of a mini series where I describe the steps needed to implement a set of Jenkins pipelines to build and deploy the code of a containerized Sitecore 10 custom solution to an existing Azure Kubernetes Services cluster resource. In the first blog post I explored in details how to setup the Jenkins build pipeline. In this blog post I am going to illustrate the second and last part of this automated deployment process: the deploy pipeline.
POSTS IN THIS SERIES:
- Jenkins Pipelines for Sitecore 10 on AKS – Part 1: The Build Pipeline
- Jenkins Pipelines for Sitecore 10 on AKS – Part 2: The Deploy Pipeline
Assumptions
Let’s start reviewing some assumptions, before diving into the details of the deploy pipeline implementation. In my Azure subscription, I already have an AKS cluster with a clean Sitecore 10 instance. If you don’t have one yet, follow the steps described in the official Sitecore Installation Guide for Production Environments with Kubernetes and read my observations in one of my previous blog post.
The Jenkins pipelines shared in this series assume the usage of a containerized solution, with the recommended folder structure for container development as shared in the custom-images section of the Sitecore Docker Examples Github repository. For this implementation, I directly used a forked version of that repository.
In this blog post I am not covering the deployment and execution of init jobs for external services, since this task would not be a step that would be constantly executed in every Jenkins deployment, but only on demand when a new Sitecore module will need to be added to an existing solution.
Software Prerequisites
The full list of software prerequisites has been described in the first blog post of this series in the Software Prerequisites section.
Kubernetes CLI Jenkins Plugin Configuration
The Kubernetes CLI, known also as kubectl command line tool, offers a set of commands to manage a Kubernetes cluster. The kubectl tool authenticates with a cluster using the token of a service account created on the cluster with the required level of access. This token and other AKS cluster API information are usually stored in a configuration file on the machine where the tool is used, called kubeconfig.
The Kubernetes CLI Jenkins plugin allows to dynamically generate a kubeconfig temporary configuration file that is deleted at the end of a stage execution where its withKubeConfig declarative command is used, avoiding to permanently store a kubeconfig file in the Jenkins virtual machine file system. The token of the account service used to authenticate with the cluster is stored in the Jenkins credentials store as a Secret Text credential and then its credentialId value is used as parameter for the withKubeConfig command in the pipeline stages.
The following commands can be used to create a service account for an existing AKS cluster and retrieve its token:
# Create a service account named `jenkins-robot` for the "sitecore" namespace
kubectl -n sitecore create serviceaccount jenkins-robot
# Grant administrator permissions to the `jenkins-robot` service account.
kubectl -n sitecore create rolebinding jenkins-robot-binding --clusterrole=cluster-admin --serviceaccount=sitecore:jenkins-robot
# Get the name of the token that was automatically generated for the ServiceAccount `jenkins-robot`.
$tokenName = kubectl -n sitecore get serviceaccount jenkins-robot -o go-template --template='{{range .secrets}}{{.name}}{{end}}'
# Retrieve the token from the "token" property in the yaml response
kubectl -n sitecore get secrets $tokenName -o yaml
# Decode the token from base64 to a string
$token = "...encoded value retrieved in previous step..."
[Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($token))
As commented in the script above, the token value retrieved using the kubectl get secrets
command is encoded in base64 format and needs to be decoded, before storing it in the Jenkins credentials store.
The Stages of the Deploy Pipeline
The last stage of the build pipeline described in the first blog post of this series pushes the built images of my custom solution to an Azure Container Registry resource. The deploy pipeline has the goal of deploying the artifacts of the build process to an existing AKS cluster resource. The deploy pipeline should be executed in a job after the build pipeline job has succeeded.
The deploy pipeline consists of four main stages (displayed in the stage view picture below) that have the main goal of identifying the correct latest version of the images built in the previous build pipeline and then performing a Kubernetes deployment to an AKS cluster using the Kubernetes CLI.

The Jenkins Deploy Pipeline Script
The deploy pipeline script, similarly to the build pipeline script, starts with the parameters section to define the Environment
parameter, used to dynamically set the name of the git repository branch that matches a specific environment (for example develop, uat, regression,…). If this Jenkins pipeline becomes part of a Jenkins Pipeline library that can be referenced in a Jenkinsfile, then the parameters section should be converted to a dynamic mapping parameters input to allow to define the input parameters in the Jenkinsfile where it is referenced.
The environment section defines the environment variables used in the stages of the pipeline. Some environment variables, like REGISTRY (or REGISTRY NAME), GIT_REPO_URL, COMPOSE_PROJECT_NAME and PRINCIPAL_SERVICE_CREDENTIAL_ID were also defined in the build pipeline and they should have the same configured values in both pipelines. New environment variables defined in the deploy pipeline include some AKS resource specific inputs, like AKS_RESOURCE_GROUP, AKS_CLUSTER_NAME, AKS_NAMESPACE and AKS_CLUSTER_API_URL, and the Kubernetes CLI token credentials, KUBERNETES_CREDENTIALS_ID.
The Checkout stage is the first of the four stages in the deploy pipeline. It consists in simply invoking the checkout command to pull the code of a GitHub repository for a specific branch. It is not strictly needed for this pipeline to exist, but it can be useful if customized Kubernetes configuration files are managed in the source code repository of the solution.
The Find Version of Latest Built Images stage has the purpose to automatically identify the version (or tag) of the latest built images available on the project ACR resource. In this step, the az acr repository show-tags
Azure CLI command is used to pull the list of available tags for one of the custom image repositories (in this implementation, the –solution
image, but any other image would work fine for this scope as well). The command is wrapped in a PowerShell script step to filter the existing tags and find the latest for the specific environment or branch specified with the dynamic pipeline Environment parameter.
The Deploy Images to AKS stage is the third stage in this pipeline and consists in invoking the kubectl set image deployments/<deployment_name>
Kubernetes CLI command to set a new image in the existing deployment configuration for a specific deployment (ie. cd, cm, …). In this stage, this command is invoked twice for the cd deployment and for the cm deployment only, but of course it can be invoked for additional deployment roles as well, if needed.
The final stage of the deploy pipeline is the Wait for Deployment Rollout to Finish stage, that checks the rollout status of the deployment resources in the AKS cluster and waits for the rollout update process of their replicas to finish.
The following script shows the full implemented Jenkins Deploy pipeline:
pipeline { | |
agent any | |
parameters { | |
string(name: 'Environment', defaultValue: 'develop', description: 'Environment/Branch name') | |
} | |
environment { | |
REGISTRY_NAME = 'myacrregistry' | |
REGISTRY = 'myacrregistry.azurecr.io/' | |
GIT_REPO_URL = 'https://github.com/afaniuolo/docker-examples.git' | |
COMPOSE_PROJECT_NAME = "mycustomsolution" | |
PRINCIPAL_SERVICE_CREDENTIAL_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" | |
AKS_RESOURCE_GROUP = 'sc10' | |
AKS_CLUSTER_NAME = 'sc10cluster' | |
AKS_NAMESPACE = 'sitecore' | |
KUBERNETES_CREDENTIALS_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' | |
AKS_CLUSTER_API_URL = 'https://sc10cluste-sc10-xxxxxx-xxxxxxxx.hcp.eastus.azmk8s.io:443' | |
VERSION = '' | |
} | |
stages { | |
stage('Checkout') { | |
steps { | |
checkout([$class: 'GitSCM', branches: [[name: '*/' + params.Environment]], extensions: [], userRemoteConfigs: [[url: GIT_REPO_URL]]]) | |
} | |
} | |
stage('Find Version of Latest Built Images') | |
{ | |
steps { | |
script { | |
azureCLI commands: [[exportVariablesString: '', script: 'az acr login --name ' + REGISTRY]], principalCredentialId: PRINCIPAL_SERVICE_CREDENTIAL_ID | |
def latest = powershell(returnStdout: true, script: '(az acr repository show-tags -n ' + REGISTRY_NAME + ' --repository ' + COMPOSE_PROJECT_NAME + '-solution --orderby time_desc --output tsv | Where-Object -FilterScript { $_ -match "' + params.Environment + '-*" })[0]') | |
VERSION = latest.trim() | |
} | |
} | |
} | |
stage('Deploy Images to AKS') { | |
steps { | |
script { | |
dir('custom-images') { | |
withKubeConfig(credentialsId: KUBERNETES_CREDENTIALS_ID, namespace: AKS_NAMESPACE, clusterName: AKS_CLUSTER_NAME, serverUrl: AKS_CLUSTER_API_URL) { | |
bat 'kubectl set image deployments/cm sitecore-xm1-cm=' + REGISTRY + COMPOSE_PROJECT_NAME + '-xm1-cm:' + VERSION + ' -n ' + AKS_NAMESPACE + ' --record' | |
bat 'kubectl set image deployments/cd sitecore-xm1-cd=' + REGISTRY + COMPOSE_PROJECT_NAME + '-xm1-cd:' + VERSION + ' -n ' + AKS_NAMESPACE + ' --record' | |
} | |
} | |
} | |
} | |
} | |
stage('Wait for Deployment Rollout to Finish') { | |
steps { | |
script { | |
dir('custom-images') { | |
withKubeConfig(credentialsId: KUBERNETES_CREDENTIALS_ID, namespace: AKS_NAMESPACE, clusterName: AKS_CLUSTER_NAME, serverUrl: AKS_CLUSTER_API_URL) { | |
bat 'kubectl rollout status deployments/cm' | |
bat 'kubectl rollout status deployments/cd' | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
A Jenkins job using this deploy pipeline takes about 2 minutes to complete and most of the time is spent in the last stage waiting for the actual rollout deployment in AKS to complete.

Future Improvements
The deploy pipeline described in this post should be considered a starting point for an automated deployment process and can be enhanced to support additional automation of tasks that are part of a release, like for example a Sitecore item synchronization task. The implementation of this task would depend of course on which item serialization tools is adopted by your solution (TDS, Unicorn or Sitecore Serialization).
Another useful improvement could be the implementation of an automatic rollback of the deployment to the latest stable release in case of a failure after images have been deployed to the AKS cluster resource. The Kubernetes CLI offers the rollout undo
command to perform a Kubernetes deployment rollback, that can be performed in a post failure
step in the Jenkins deploy pipeline.
Conclusions
This blog post described the implementation of the Jenkins deploy pipeline to deploy a containerized Sitecore 10 solution to an Azure Kubernetes Service cluster. I hope that this mini series helps you to implement your own pipeline with Jenkins or, if you are using a different CI/CD tool, helps you to understand the needed stages to implement it using your own tool. If you have any questions please don’t hesitate to reach out or comment on this post.
Thank you for reading!