Adding a new language to the platform
This guide covers how to add first-class support for a new programming language on the HMCTS platform. Follow the checklist below in order — each step links to its reference section.
We will reference recent work to add Python support to the platform, but the same steps apply to any language.
Checklist
- [ ] Import the upstream base image into ACR
- [ ] Create the base Docker image
- [ ] Create the base Helm chart
- [ ] Create service templates
- [ ] Update the Jenkins pipeline
- [ ] Create a test application
- [ ] Deploy to AKS via Flux
- [ ] Write documentation and the golden path
1. Import the upstream base image
Before building an HMCTS base image you need the upstream image available in our ACR. This is managed by acr-base-importer.
Two things to add:
A. Import the image — add an entry to baseImagestoImport in trivy-scan-import.yml. This imports the image into hmctsprod (and optionally hmctssbox) on a daily schedule, scanning for critical vulnerabilities before import.
- baseRegistry: 'gcr.io'
baseImage: 'distroless/python3-debian13'
baseTag: 'latest'
targetImage: 'imported/distroless/python3'
B. Import any additional images (optional) — depending on the language, teams may need additional upstream images available in ACR beyond the base image. For example, Python requires a multi-stage Docker build where the build stage uses a full Python image (with uv/pip and build tooling) to install dependencies, before copying the result into the distroless runtime image.
If the language runtime image is self-contained and no additional images are needed at build time, this step can be skipped.
How an additional image is made available depends on its source registry:
docker.ioimages — create a cache rule inacr-repositories.yaml. This proxies pulls throughhmctsprodrather than hitting Docker Hub directly.ghcr.ioimages — add an import entry totrivy-scan-import.yml, the same way as the base image in step A.
See the acr-base-importer README for the full configuration reference.
2. Create the base Docker image
Create a new repository named cnp-{language}-base in the hmcts GitHub organisation.
The base image sits between the upstream distroless/runtime image and application Dockerfiles. Its responsibilities are:
- Installing the language runtime and package manager
- Pre-wiring observability tooling (Azure Application Insights via OpenTelemetry)
- Providing a consistent, security-scanned foundation for all services using that language
The application Dockerfile then extends the base image with only application-specific layers.
Reference: cnp-python-base — includes the build pipeline, Application Insights wiring, and a README documenting the environment variables available to applications.
3. Create the base Helm chart
Create a new repository named chart-{language} in the hmcts GitHub organisation.
The base Helm chart provides the standard Kubernetes deployment configuration for services written in that language. It extends chart-library, which provides the common HMCTS Helm templates (Deployment, Service, Ingress, etc.). The language-specific chart adds sensible defaults on top — things like the application port, health probe paths, and any language-specific sidecar configuration.
It should cover:
- Deployment, Service, and Ingress resources
- Readiness and liveness probe paths (e.g.
/health/readiness,/health/liveness) - Key Vault secret mounting via the CSI driver
- Environment variable configuration
- HPA (Horizontal Pod Autoscaler) configuration
- PostgreSQL sidecar support (if applicable)
The chart is published to hmcts-charts via the release pipeline so it can be consumed as a Helm dependency by application charts.
Reference: chart-python — see its README for the full set of configurable values.
4. Create service templates
Two templates are needed: one for GitHub and one for Backstage. Both scaffold a new service that is immediately pipeline-compatible.
GitHub template
Create a repository named {framework}-template-github (e.g. fastapi-template-github).
The template must include:
- Application scaffold with the framework installed and a working entry point
GET /health,GET /health/readiness,GET /health/livenessendpointsJenkinsfile_CNP,Jenkinsfile_nightly,Jenkinsfile_parameterizedsonar-project.properties- Helm chart in
charts/using the base Helm chart values.preview.template.yamlandvalues.aat.template.yaml- GitHub Actions CI workflow (lint, test, coverage)
.python-versionor equivalent runtime version file- Lock file committed (e.g.
uv.lock) README.mdcovering local setup, testing, dependencies, security scanning, and Application Insights
Reference: fastapi-template-github
Backstage template
Create a repository named {framework}-template-backstage (e.g. fastapi-template-backstage). This wraps the GitHub template with Backstage scaffolding so it appears in the HMCTS software catalogue under “Create”.
The skeleton/ directory mirrors the GitHub template structure, with Backstage template variables (e.g. ${{ values.product }}, ${{ values.component }}) substituted throughout.
Reference: fastapi-template-backstage
Registering in the Backstage catalogue
Once the template repository is created, register it in backstage-portal so it appears under “Create” in the software catalogue. Templates are registered in app-config.production.yaml — add an entry pointing to the template.yaml in your new Backstage template repository.
5. Update the Jenkins pipeline
The common pipeline (cnp-jenkins-library) needs four changes to support a new language.
Builder class
Create src/uk/gov/hmcts/contino/{Language}Builder.groovy extending AbstractBuilder. Implement at minimum:
| Method | Purpose |
|---|---|
build() |
Install dependencies |
test() |
Run unit tests with coverage |
securityCheck() |
Run dependency vulnerability audit, archive report, fail on findings |
sonarScan() |
Run SonarCloud scanner |
smokeTest() |
Run smoke tests post-deploy |
functionalTest() |
Run functional tests post-deploy |
Reference: PythonBuilder.groovy
PipelineType class
Create src/uk/gov/hmcts/contino/{Language}PipelineType.groovy. This wires the Builder to the pipeline and declares the language identifier.
Reference: PythonPipelineType.groovy
Register in pipeline entry points
Add an import and an entry to the pipelineTypes map in all three pipeline files:
vars/withPipeline.groovyvars/withParameterizedPipeline.groovyvars/withNightlyPipeline.groovy
import uk.gov.hmcts.contino.PythonPipelineType
// in pipelineTypes map:
python: { new PythonPipelineType(this, product, component) }
Tests
Add a corresponding test class at test/uk/gov/hmcts/contino/{Language}BuilderTest.groovy. All existing tests must continue to pass.
Run the test suite locally before raising a PR:
./gradlew test
6. Create a test application
Before announcing support, validate the full pipeline end-to-end with a real application in the hmcts GitHub organisation.
Use the GitHub or Backstage template to scaffold the app. The test application should exercise:
- The full Jenkins pipeline (build, test, Sonar, security check, Docker build, preview deploy)
- Smoke and functional tests running post-deploy
- The base Helm chart values files
- Application Insights (if wired in the base image)
Reference: cnp-plum-fastapi-backend
7. Deploy to AKS via Flux
Once the test application is working in preview, register it in the appropriate Flux config repo so it is deployed and managed across all environments.
- CFT clusters: cnp-flux-config — see the app deployment guide
- SDS clusters: sds-flux-config — follows the same process
8. Write documentation and the golden path
Once support is validated end-to-end, create the developer-facing documentation.
Developer documentation
Add a page to hmcts.github.io under source/cloud-native-platform/new-component/. Follow the same structure as the existing developer docs.
Reference: Python services guide and its source.