A Helm chart for deploying NBomber Studio on Kubernetes.
This chart deploys:
- An NBomber Studio Deployment
- A ServiceAccount, Role, and RoleBinding scoped to the test namespace
- A dedicated test namespace (default:
nbomber-tests) for running NBomber test Jobs and Pods - A ClusterIP service for internal access
- Optional ConfigMap and Secret for configuration overrides
- Optional Ingress resource for external HTTPS access
NBomber Studio automatically creates a dedicated Kubernetes namespace (default: nbomber-tests) where it deploys and runs NBomber test Jobs and Pods.
Dependencies: NBomber Studio requires PostgreSQL with the TimescaleDB extension for storing test metrics and results. Make sure a TimescaleDB-enabled PostgreSQL instance is accessible before installing this chart. The NBomber team provides a ready-to-use Timescale Helm chart on Artifact Hub to simplify this setup.
It is designed for simplicity and can be used in development, testing, or production environments.
Step 1 — Add the Helm repository
helm repo add nbomber-studio https://pragmaticflow.github.io/nbomber-studio-helm/
helm repo updateStep 2 — Install the chart with the required Secret
A PostgreSQL connection string is required. Set
config.secret.enabled=truewithconfig.secret.data.PostgreSql.ConnectionString, or setconfig.existingSecretto the name of an existing Secret. Make sure to use the full K8s domain name for the PostgreSQL host (e.g.nbomber-timescale.default.svc.cluster.local) so that NBomber test Jobs running in a separate namespace can reach PostgreSQL.
Option A — without authentication (for local/dev environments only):
Inline with --set flags:
Use the full Kubernetes domain hostname for the PostgreSQL host (
<service>.<namespace>.svc.cluster.local). A short hostname will fail because NBomber test Jobs run in a separate namespace.
Set
Auth.Enabled=falseto skip JWT configuration entirely.
helm install nbomber-studio nbomber-studio/nbomber-studio \
--set config.secret.enabled=true \
--set-string "config.secret.data.PostgreSql.ConnectionString=Host=<timescaledb-service>.<namespace>.svc.cluster.local;Port=5432;Username=<user>;Password=<password>;Database=<db>" \
--set config.secret.data.Auth.Enabled=falseOr in a values.yaml file:
config:
secret:
enabled: true
data:
PostgreSql:
ConnectionString: "Host=<timescaledb-service>.<namespace>.svc.cluster.local;Port=5432;Username=<user>;Password=<password>;Database=<db>;Pooling=true;Maximum Pool Size=300;"
Auth:
Enabled: falsehelm install nbomber-studio nbomber-studio/nbomber-studio -f my-values.yamlOption B — with authentication enabled (recommended for production):
Inline with --set flags:
helm install nbomber-studio nbomber-studio/nbomber-studio \
--set config.secret.enabled=true \
--set-string "config.secret.data.PostgreSql.ConnectionString=Host=<timescaledb-service>.<namespace>.svc.cluster.local;Port=5432;Username=<user>;Password=<password>;Database=<db>" \
--set-string "config.secret.data.Auth.JwtSecret=<your-secure-jwt-secret>"Using a values.yaml file (recommended for complex connection strings):
config:
secret:
enabled: true
data:
PostgreSql:
ConnectionString: "Host=<timescaledb-service>.<namespace>.svc.cluster.local;Port=5432;Username=<user>;Password=<password>;Database=<db>;Pooling=true;Maximum Pool Size=300;"
Auth:
JwtSecret: "<your-secure-jwt-secret>" # generate with: openssl rand -base64 32Upgrade
helm upgrade nbomber-studio nbomber-studio/nbomber-studio -f my-values.yamlThe following table lists the configurable parameters of the chart and their default values:
| Parameter | Description | Default |
|---|---|---|
nameOverride |
Override the chart name | "" |
fullnameOverride |
Override the full name of resources | "" |
image.repository |
NBomber Studio image repository | nbomberdocker/nbomber-studio |
image.tag |
Image tag (defaults to Chart appVersion) | "" |
image.pullPolicy |
Image pull policy | IfNotPresent |
service.type |
Kubernetes service type | ClusterIP |
service.port |
Service port | 80 |
resources.limits.memory |
Memory limit | 2Gi |
resources.requests.memory |
Memory request | 500Mi |
config.configMap.enabled |
Create ConfigMap for config overrides | false |
config.configMap.data |
ConfigMap data (JSON structure) | {} |
config.secret.enabled |
Create Secret for config overrides | false |
config.secret.data |
Secret data (JSON structure) | {} |
config.secret.data.License |
NBomber Studio license key | "" |
config.existingConfigMap |
Use existing ConfigMap | "" |
config.existingSecret |
Use existing Secret | "" |
ingress.enabled |
Enable ingress resource | false |
ingress.className |
Ingress class name | "traefik" |
ingress.annotations |
Ingress annotations | {} |
ingress.hosts |
Ingress host configurations | see values.yaml |
ingress.tls |
Ingress TLS configuration | [] |
serviceAccount.create |
Create a ServiceAccount for the Studio pod | true |
serviceAccount.name |
ServiceAccount name (auto-generated if empty) | "" |
serviceAccount.annotations |
Annotations to add to the ServiceAccount | {} |
testNamespace.name |
Namespace where NBomber test Jobs/Pods are deployed | nbomber-tests |
testNamespace.create |
Create the test namespace (set to false if it already exists) |
true |
trustedCAs.enabled |
Mount additional trusted CA cert(s) into the trust bundle | false |
trustedCAs.certs |
Inline PEM CA cert(s); chart creates a Secret from these | {} |
trustedCAs.existingSecret |
Use an existing Secret of PEM CA cert(s) instead of certs |
"" |
nodeSelector |
Node labels for pod assignment | {} |
affinity |
Affinity rules | {} |
tolerations |
Tolerations for pod assignment | [] |
This chart automatically provisions the Kubernetes RBAC resources needed for NBomber Studio to manage test workloads:
| Resource | Name | Scope |
|---|---|---|
| Namespace | nbomber-tests (configurable) |
cluster |
| ServiceAccount | <release-name>-nbomber-studio |
Studio's namespace |
| Role | <release-name>-nbomber-studio-test-manager |
test namespace |
| RoleBinding | <release-name>-nbomber-studio-test-manager |
test namespace |
The Role grants the Studio pod permissions to manage the following resources only within the test namespace:
pods,pods/log— get, list, watch, create, deletesecrets— get, list, create, update, patch, deleteservices— get, list, create, deletedeployments(apps) — get, list, watch, create, deletejobs(batch) — get, list, watch, create, delete
To use an existing namespace instead of creating a new one:
testNamespace:
name: "my-existing-namespace"
create: falseTo use an existing ServiceAccount:
serviceAccount:
create: false
name: "my-service-account"This Helm chart allows you to override configuration values using Kubernetes ConfigMaps and Secrets. The configuration override system works as follows:
- ConfigMap - For non-sensitive settings (logging levels, feature flags, etc.)
- Secret - For sensitive settings (database passwords, JWT secrets, etc.)
- Both are mounted into the container.
Override sensitive settings using a Secret:
config:
secret:
enabled: true
data:
PostgreSql:
ConnectionString: "Host=nbomber-timescale.default.svc.cluster.local;Port=5432;Username=user;Password=pass;Database=mydb"
Auth:
JwtSecret: "your-secure-jwt-secret"Important: Always use the full Kubernetes FQDN for the PostgreSQL host:
<service>.<namespace>.svc.cluster.localNBomber test Jobs run in a dedicated test namespace (see
testNamespace), which is separate from the namespace where PostgreSQL is installed. A short hostname resolves only within the same namespace and will cause connection failures from the test Jobs.
Override non-sensitive settings using a ConfigMap:
config:
configMap:
enabled: true
data:
Logger:
MinimumLogLevel: "Debug"If you manage secrets externally (e.g., via Sealed Secrets, External Secrets Operator):
config:
existingSecret: "my-nbomber-secret"
existingConfigMap: "my-nbomber-config"Create a secret from command line:
kubectl create secret generic nbomber-studio-config \
--from-literal=config-override.json='{
"PostgreSql": {
"ConnectionString": "Host=nbomber-timescale.default.svc.cluster.local;Port=5432;..."
},
"Auth": {
"JwtSecret": "my-secret-key"
}
}'Then reference it:
config:
existingSecret: "nbomber-studio-config"The override configuration follows the same JSON structure as the default config.json:
{
"PostgreSql": {
"ConnectionString": "Host=nbomber-timescale.default.svc.cluster.local;Port=5432;..."
},
"Logger": {
"MinimumLogLevel": "Information"
},
"Auth": {
"Enabled": true,
"JwtSecret": "your-secret-here",
"StaticUserAuth": {
"Users": [
{
"Email": "admin@admin",
"Hash": "$2y$10$...",
"UserName": "admin"
}
]
}
}
}When enabled, the following files are mounted in the container:
/app/config-override-cm.json- ConfigMap overrides/app/config-override-secret.json- Secret overrides
If NBomber Studio must talk to an internally-issued HTTPS endpoint — for example a private OIDC/JWT issuer — the issuer's CA must be trusted by the container. NBomber Studio runs on .NET, which on Linux validates TLS against the OpenSSL system trust store, so it is not enough to simply place a .crt file in the pod.
Enabling trustedCAs runs a small init container that merges the supplied CA certificate(s) into the system trust bundle and points the app at the result via SSL_CERT_FILE. Publicly-trusted CAs continue to work, because the merged bundle still contains the default roots.
Provide the CA inline (the chart creates the Secret):
trustedCAs:
enabled: true
certs:
internal-ca.crt: |
-----BEGIN CERTIFICATE-----
...your internal CA...
-----END CERTIFICATE-----Or reference an existing Secret (e.g. one produced by cert-manager or External Secrets). Every key in the Secret is treated as a PEM file and appended to the bundle:
trustedCAs:
enabled: true
existingSecret: "my-internal-ca"The init container reuses the NBomber Studio image, so the base trust bundle always matches the runtime image and no extra image is pulled.
A license key is required to use NBomber Studio. Set it via the License field inside the secret data:
config:
secret:
enabled: true
data:
License: "<your-license-key>"
PostgreSql:
ConnectionString: "Host=<timescaledb-service>.<namespace>.svc.cluster.local;Port=5432;Username=<user>;Password=<password>;Database=<db>"
Auth:
JwtSecret: "<your-secure-jwt-secret>"Or with --set:
helm install nbomber-studio nbomber-studio/nbomber-studio \
--set config.secret.enabled=true \
--set-string "config.secret.data.License=<your-license-key>" \
--set-string "config.secret.data.PostgreSql.ConnectionString=Host=<timescaledb-service>.<namespace>.svc.cluster.local;Port=5432;Username=<user>;Password=<password>;Database=<db>" \
--set-string "config.secret.data.Auth.JwtSecret=<your-secure-jwt-secret>"- Always use Secrets for sensitive data like passwords, JWT secrets, connection strings
- Use ConfigMaps for non-sensitive data like log levels, timeouts, feature flags
- Consider using external secret management tools like:
- Sealed Secrets
- External Secrets Operator
- HashiCorp Vault
- AWS Secrets Manager / Azure Key Vault / GCP Secret Manager
To expose NBomber Studio externally with HTTPS/TLS, enable the ingress resource:
ingress:
enabled: true
className: "traefik" # or "nginx", depending on your ingress controller
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
hosts:
- host: nbomber-studio.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: nbomber-studio-tls
hosts:
- nbomber-studio.example.comingress:
enabled: true
className: "nginx"
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: nbomber-studio.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: nbomber-studio-tls
hosts:
- nbomber-studio.example.comingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: nbomber-studio.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: nbomber-studio-tls
hosts:
- nbomber-studio.example.comSee values-example.yaml for complete examples.
See HTTPS-SETUP.md for a detailed guide on enabling HTTPS/TLS.
config:
secret:
enabled: true
data:
PostgreSql:
ConnectionString: "Host=prod-timescaledb.database.svc.cluster.local;Port=5432;Username=nbomber_prod;Password=SecureP@ssw0rd;Database=nb_studio_prod;Pooling=true;Maximum Pool Size=300;"
Auth:
JwtSecret: "prod-jwt-secret-generate-with-openssl-rand-base64-32"
JwtExpireSec: 3600config:
configMap:
enabled: true
data:
Logger:
MinimumLogLevel: "Debug"