Gérer les versions de ses charts Helm peut rapidement devenir un casse-tête : un dépôt par chart, ça reste simple avec 2 ou 3 charts… beaucoup moins quand on en gère une dizaine.
Pour mes propres charts, j'utilise Nx au sein d'un monorepo. C'est ce setup que je détaille ici.
L'idée vient d'Antoine Caron et de son article Managing Terraform Modules with Nx Monorepo.
Qu'est-ce que Nx ?
Nx est un outil de build conçu pour gérer des monorepos. À l'origine orienté vers les technologies web, il est compatible avec tous les langages et permet d'orchestrer des tâches (lint, build, test, release) sur des projets indépendants au sein d'un même dépôt.
Configuration d'un environnement Nx pour Helm
Voici la structure que j'utilise pour un monorepo de charts Helm.
Création du workspace Nx
npx create-nx-workspace@latest helm-charts --preset=npm
cd helm-chartsArborescence
- helm-charts
- charts
- example-chart
- templates
- Chart.yaml
- package.json
- nx.json
- package.json
Chaque chart a son dossier sous charts/ et un package.json pour la config Nx.
Configuration du monorepo
Créons le fichier nx.json avec la configuration suivante :
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"release": {
"projects": ["charts/*"],
"projectsRelationship": "independent",
"releaseTagPattern": "{projectName}@v{version}",
"version": {
"conventionalCommits": true
},
"changelog": {
"projectChangelogs": {
"createRelease": "github",
"file": "{projectRoot}/CHANGELOG.md",
"renderOptions": {
"authors": false,
"versionTitleTemplate": "## {version} ({date})",
"commitReferences": true,
"versionTitleDate": true
}
}
},
"git": {
"commit": true,
"tag": true,
"commitMessage": "chore(release): {projectName} {version}"
}
}
}Quelques points clés :
projectsRelationship: "independent"— chaque chart a sa propre version.releaseTagPattern— les tags seront formatés enexample-chart@v1.2.3.conventionalCommits— la version est définie automatiquement grâce aux messages de commit.
Configuration par chart
Chaque chart dispose d'un package.json qui décrit ses targets Nx :
{
"name": "example-chart",
"version": "0.1.0",
"scripts": {
"post-release": "nx run example-chart:sync-chart-version"
},
"nx": {
"targets": {
"lint": {
"executor": "nx:run-commands",
"options": {
"command": "helm lint --strict .",
"cwd": "charts/example-chart"
}
},
"sync-chart-version": {
"executor": "nx:run-commands",
"options": {
"command": "node ../../scripts/sync-chart-version.mjs example-chart",
"cwd": "charts/example-chart"
}
},
"package": {
"executor": "@nx-extensions/helm:package",
"options": {
"chartFolder": "charts/example-chart",
"outputFolder": "{workspaceRoot}/dist/charts/{projectRoot}",
"remote": "oci://localhost:5000/helm-charts"
}
}
}
}
}On définit ici trois targets :
lint— valide le lint du chart avec la commandehelm lint.sync-chart-version— synchronise la version dupackage.jsonNx vers le fichierChart.yaml.package— empaquette et publie le chart via l'extension @nx-extensions/helm.
Script de synchronisation de version
Le script sync-chart-version.mjs lit la version depuis le package.json du projet Nx et l'injecte dans le Chart.yaml :
import { readFileSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const chartName = process.argv[2];
const chartDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'charts', chartName);
const { version } = JSON.parse(readFileSync(join(chartDir, 'package.json'), 'utf8'));
const chartPath = join(chartDir, 'Chart.yaml');
const chart = readFileSync(chartPath, 'utf8');
writeFileSync(chartPath, chart.replace(/^version: .+$/m, `version: ${version}`));Votre monorepo est prĂŞt.
Le flux de release
Voici l'enchaînement lors d'une release :
- Un développeur pousse un commit sur la branche principale
- Nx détecte les projets concernés par le commit
- Pour chaque chart modifié,
nx releaseexécute :- Calcul de la nouvelle version (conventional commits)
- Mise Ă jour du
CHANGELOG.md - Création du tag Git (
example-chart@v1.2.3) - Exécution du hook
post-releasequi appellesync-chart-versionpuispackage
- Le chart est publié dans un registry OCI
Déclenchement manuel
# Release d'un chart spécifique
nx release example-chart --skip-publish
# Release de tous les charts modifiés
nx release --projects=charts/*Vous pouvez faire un test Ă blanc en ajoutant l'option
--dry-run.
CI/CD avec GitHub Actions
name: Nx CI
on:
push:
branches: ["main"]
jobs:
lint-release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 12
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run lint
run: npx nx affected -t lint
- name: Run Nx release
if: github.ref == 'refs/heads/main'
run: CI=true npx nx release --skip-publish
- name: Package and push Helm charts
if: github.ref == 'refs/heads/main'
run: npx nx run-many -t package --projects=charts/*
- name: Chart post-release tasks
if: github.ref == 'refs/heads/main'
run: |
npx nx run-many -t post-release --projects=charts/*
git add charts/
# if there are changes to commit, commit and push them
if [ -n "$(git status --porcelain)" ]; then
git commit -m "chore(post-release): sync chart version"
git push
else
echo "No changes detected. Skipping commit."
fiDépôt exemple
Le dépôt christian-vdz/helm-charts tourne avec cette config et contient actuellement plusieurs charts :
deployment-start-stop— monte ou descend le nombre de réplicas d'un déploiementdreeve— déploie Dreeve
Chaque chart suit le même modèle : lint → nx release (conventional commits) → synchro de la version dans Chart.yaml et doc → publication OCI.
La doc est générée avec hextra et accessible sur christian-vdz.github.io/helm-charts.
Conclusion
Nx centralise le versionnage de vos charts Helm dans un monorepo, sans dupliquer l'outillage. Chaque chart garde sa propre version, les changelogs sont générés automatiquement, et la publication OCI s'intègre dans la CI.
Le même modèle se décline pour d'autres projets (packages npm, modules Terraform, images Docker, etc.). Une fois en place, il n'y a plus qu'à dupliquer.