Skip to main content

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

  1. [ ] Import the upstream base image into ACR
  2. [ ] Create the base Docker image
  3. [ ] Create the base Helm chart
  4. [ ] Create service templates
  5. [ ] Update the Jenkins pipeline
  6. [ ] Create a test application
  7. [ ] Deploy to AKS via Flux
  8. [ ] 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.io images — create a cache rule in acr-repositories.yaml. This proxies pulls through hmctsprod rather than hitting Docker Hub directly.
  • ghcr.io images — add an import entry to trivy-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/liveness endpoints
  • Jenkinsfile_CNP, Jenkinsfile_nightly, Jenkinsfile_parameterized
  • sonar-project.properties
  • Helm chart in charts/ using the base Helm chart
  • values.preview.template.yaml and values.aat.template.yaml
  • GitHub Actions CI workflow (lint, test, coverage)
  • .python-version or equivalent runtime version file
  • Lock file committed (e.g. uv.lock)
  • README.md covering 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.groovy
  • vars/withParameterizedPipeline.groovy
  • vars/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.


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.

This page was last reviewed on 23 June 2026. It needs to be reviewed again on 23 June 2027 by the page owner platops-build-notices .
This page was set to be reviewed before 23 June 2027 by the page owner platops-build-notices. This might mean the content is out of date.