Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

80 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BaSyx Go Helm Charts

This repository contains a Helm chart for deploying an Eclipse BaSyx Go based environment on Kubernetes.

The chart installs the BaSyx Go backend services, a PostgreSQL database, optional Keycloak-based authentication, optional ABAC authorization, ingress resources, certificates, the AAS Web UI and optional supporting runtime tests.

The repository follows the common Helm multi-chart layout:

charts/basyx/           Helm chart for BaSyx Go
examples/postman/       Postman collections for example setups
values/                 Custom values examples and deployment overlays

The main chart is located at charts/basyx. All commands below are written from the repository root.

What Gets Deployed

The basyx chart can deploy these components:

Component Purpose
keycloak Identity provider for the Web UI and secured BaSyx services
aasDiscovery AAS discovery service
aasRegistry AAS registry service
aasRepository AAS repository service
aasEnvironment Integrated AAS environment service bundling AAS, submodel, concept description, registry and discovery APIs
dppApi Digital Product Passport API service
submodelRegistry Submodel registry service
submodelRepository Submodel repository service
cdRepository Concept description repository
companyLookup Company endpoint directory for dataspace participants
digitalTwinRegistry Digital twin registry
aasWebGui Web UI for browsing and uploading AAS data
database PostgreSQL cluster managed by CloudNativePG
configurationService One-shot BaSyx Go job that initializes and migrates the PostgreSQL schema

How The Deployment Works

A deployment is one Helm release, usually named basyx, installed into one Kubernetes namespace.

The default chart values live in:

charts/basyx/values.yaml

Custom values files live outside the chart, for example:

values/values.catena-x.example.yaml
values/values.example.yaml
values/values.minimal.yaml
values/values.observability.example.yaml
values/values.autoscaling.example.yaml
values/values.secured.example.yaml

A custom values file usually defines the public host, enabled services, image tags, TLS settings, and optional security settings such as Keycloak users or ABAC rules.

Prerequisites

You need:

  • A Kubernetes cluster and a working kubectl context
  • Helm 3
  • An ingress controller, for example nginx ingress or Azure Application Gateway Ingress Controller
  • cert-manager, because the chart renders cert-manager.io/v1 resources
  • CloudNativePG, because the chart renders postgresql.cnpg.io/v1 database clusters
  • Optional: helm-unittest for local chart tests

Check the current cluster context before installing:

kubectl config current-context
kubectl get nodes

If you work with multiple clusters, pass the context explicitly:

--kube-context custom-rke

Install Required Operators

Install CloudNativePG if it is not already installed:

helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update

helm upgrade --install cnpg cnpg/cloudnative-pg \
  --namespace cnpg-system \
  --create-namespace

Install cert-manager if it is not already installed:

helm repo add jetstack https://charts.jetstack.io --force-update
helm repo update

helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.19.3 \
  --set crds.enabled=true

Verify the required CRDs are available:

kubectl api-resources | grep cert-manager.io
kubectl api-resources | grep postgresql.cnpg.io

Create A Custom Values File

Start by copying one of the provided example values files:

# Unsecured deployment without Keycloak and ABAC.
cp values/values.example.yaml values/values.my-environment.yaml

# Minimal deployment with only AAS Environment and Web UI.
cp values/values.minimal.yaml values/values.my-minimal-environment.yaml

# Secured deployment with Keycloak and ABAC.
cp values/values.secured.example.yaml values/values.my-secured-environment.yaml

# Catena-X oriented deployment with marker-based DTR and Submodel access.
cp values/values.catena-x.example.yaml values/values.my-catena-x-environment.yaml

# Optional logging and tracing overlay for an existing Collector.
cp values/values.observability.example.yaml values/values.my-observability.yaml

# Optional HPA overlay for a BaSyx Go backend.
cp values/values.autoscaling.example.yaml values/values.my-autoscaling.yaml

The unsecured example enables the core BaSyx Go services and the Web UI:

instanceName: example
host: basyx.example.com

tls:
  enabled: true
  hosts:
    - basyx.example.com

ingress:
  issuer: internal-issuer

internal:
  certificateIssuer:
    name: internal-issuer
    autocreateCa: true
    autocreateCaSecretName: internal-issuer-ca

keycloak:
  enabled: false

aasDiscovery:
  enabled: true
aasRegistry:
  enabled: true
aasRepository:
  enabled: true
aasEnvironment:
  enabled: false
dppApi:
  enabled: false
submodelRegistry:
  enabled: true
submodelRepository:
  enabled: true
cdRepository:
  enabled: true
companyLookup:
  enabled: false
aasWebGui:
  enabled: true
  infrastructureConfig:
    infrastructures:
      default: main
      main:
        security:
          type: None

abac:
  enabled: false

The aasWebGui.infrastructureConfig...security.type: None override is intentional for unsecured deployments. Without it, the Web UI would inherit the chart's secured default configuration.

Use values/values.secured.example.yaml when you want authentication and authorization enabled from the start. It enables Keycloak, initializes an example admin user and enables ABAC with the chart default rule that grants full access to users with token claim role=admin.

Use values/values.minimal.yaml when you want the BaSyx Go MinimalExample style deployment. It enables only aasEnvironment and aasWebGui; the individual AAS/Submodel registries and repositories stay disabled because the AAS Environment exposes those APIs through one component.

For DPP API deployments, enable dppApi in your own values file. DPP commonly runs together with aasEnvironment and aasWebGui, but it is configured through the same service block pattern as the other optional BaSyx Go services.

Use values/values.catena-x.example.yaml when you want a Catena-X oriented setup. It enables Keycloak, ABAC, Digital Twin Registry and Submodel Repository with BPN-based marker access rules.

Use values/values.observability.example.yaml as an overlay when BaSyx logs should use JSON and traces should be exported to an existing OpenTelemetry Collector. It does not deploy an observability backend.

Do not publish production passwords or client secrets. For public examples, use placeholders and inject real credentials through your deployment pipeline or an external secret management solution.

Render Before Installing

Always render the chart before the first install. This catches schema errors and missing CRDs early:

helm lint charts/basyx -f values/values.example.yaml
helm lint charts/basyx -f values/values.minimal.yaml
helm lint charts/basyx -f values/values.secured.example.yaml
helm lint charts/basyx -f values/values.catena-x.example.yaml
helm lint charts/basyx -f values/values.minimal.yaml -f values/values.observability.example.yaml
helm lint charts/basyx -f values/values.example.yaml -f values/values.autoscaling.example.yaml

helm template basyx charts/basyx \
  -n basyx-custom \
  -f values/values.example.yaml

If you use chart-local custom certificates, render from the repository root so the chart can read files under charts/basyx/config-files/.

Install BaSyx Go

Install the release:

helm upgrade --install basyx charts/basyx \
  --kube-context custom-rke \
  -n basyx-custom \
  --create-namespace \
  -f values/values.example.yaml

If you are already on the correct Kubernetes context, --kube-context is optional:

helm upgrade --install basyx charts/basyx \
  -n basyx-custom \
  --create-namespace \
  -f values/values.example.yaml

Check The Deployment

Check the Helm release:

helm --kube-context custom-rke status basyx -n basyx-custom
helm --kube-context custom-rke history basyx -n basyx-custom

Check Kubernetes resources:

kubectl --context custom-rke get pods,svc,ingress \
  -n basyx-custom

Wait for a service rollout:

kubectl --context custom-rke rollout status \
  deployment/basyx-aas-registry \
  -n basyx-custom

Check logs:

kubectl --context custom-rke logs \
  -n basyx-custom \
  deploy/basyx-aas-registry \
  -c aas-registry

Run runtime Helm tests:

helm --kube-context custom-rke test basyx \
  -n basyx-custom \
  --logs

Access The Services

With the default paths, services are exposed below one host:

Service URL pattern
AAS Web UI https://<host>/aas-gui/
Keycloak https://<host>/identity-management
AAS Discovery https://<host>/aas-discovery
AAS Registry https://<host>/aas-registry
AAS Repository https://<host>/aas-repository
AAS Environment https://<host>/aas-environment
DPP API https://<host>/dpp-api/v1/dpps
Submodel Registry https://<host>/submodel-registry
Submodel Repository https://<host>/submodel-repo or your custom override
Concept Description Repository https://<host>/cd-repository
Company Lookup https://<host>/company-lookup/companies
Digital Twin Registry https://<host>/digital-twin-registry

Company Lookup intentionally has no resource at the bare /company-lookup path. Use /company-lookup/companies, /company-lookup/description, /company-lookup/swagger or /company-lookup/health.

DPP API exposes its main API below /dpp-api/v1/dpps, Swagger UI below /dpp-api/swagger, OpenAPI below /dpp-api/api-docs/openapi.yaml and health below /dpp-api/health when using the default path.

Upgrade A Deployment

Change the custom values and run:

helm upgrade basyx charts/basyx \
  --kube-context custom-rke \
  -n basyx-custom \
  -f values/values.example.yaml

For safer upgrades, render first and optionally use the Helm diff plugin:

helm diff upgrade basyx charts/basyx \
  --kube-context custom-rke \
  -n basyx-custom \
  -f values/values.example.yaml

Upgrading to chart 3.6.0

Chart 3.6.0 changes the BaSyx Go per-pod PostgreSQL pool defaults from 500 open and idle connections to 50 open and 25 idle connections. This prevents a small number of replicas from exhausting a typical PostgreSQL connection budget, but workloads that relied on the previous limits can see more request queuing after the upgrade.

Review the aggregate connection budget and load-test the new limits before upgrading. To preserve the previous limits temporarily, set them explicitly:

environment:
  common:
    POSTGRES_MAXOPENCONNECTIONS: 500
    POSTGRES_MAXIDLECONNECTIONS: 500

Only retain those values when PostgreSQL has enough memory and connection capacity for every replica, migration job, identity service, and operational client. Chart 3.6.0 uses BaSyx Go 1.0.5 by default because the idle-time setting and the documented zero-value and validation behavior require that release.

Uninstall

Uninstall the Helm release:

helm uninstall basyx \
  --kube-context custom-rke \
  -n basyx-custom

CloudNativePG database PVCs and manually created secrets may need separate cleanup, depending on your cluster retention policy.

Configuration Overview

Global Values

Value Description
instanceName Logical deployment name. Useful for templated certificate paths and UI labels.
host Public DNS host used by ingress, Keycloak issuer URLs and Web UI URLs.
nameOverride Overrides the chart name used in generated resource names.
fullnameOverride Overrides the generated release name.
paths.* Public URL paths for the services. All paths should start with /.
environment.common.* Shared environment variables loaded by BaSyx backend services.

TLS And Certificates

Value Description
tls.enabled Enables TLS-related mounts and ingress TLS rendering.
tls.hosts Hosts included in rendered TLS resources.
tls.secretName Existing TLS Secret used by all generated ingress resources. Defaults to <release-name>-tls-secret when empty.
ingress.className Global spec.ingressClassName default inherited by all service ingress resources. Defaults to nginx.
ingress.annotations Global ingress annotations merged into all service ingress resources. Service-local annotations override global annotations.
ingress.issuer Existing namespaced cert-manager issuer for ingress certificates.
ingress.clusterIssuer Existing cluster issuer for ingress certificates.
internal.certificateIssuer.autocreateCa Creates an internal self-signed CA and issuer.
internal.certificateIssuer.name Internal issuer name used by generated certificates.

The chart sets spec.privateKey.rotationPolicy: Always on the generated CA certificate to avoid cert-manager v1.18+ default-change warnings.

Ingress Controllers

The chart defaults to nginx ingress, but the generated Kubernetes Ingress resources are not nginx-specific. Configure another ingress controller globally with ingress.className and ingress.annotations; individual services can still override those settings under <service>.ingress.

Service routes use Kubernetes pathType: Prefix by default because it is portable across common ingress controllers. Override <service>.ingress.hosts[].paths[].pathType only when your controller explicitly requires another mode such as ImplementationSpecific.

Example for Azure Application Gateway Ingress Controller:

host: basyx.example.com

tls:
  enabled: true
  secretName: tls-secret
  hosts:
    - "{{ .Values.host }}"

ingress:
  className: azure-application-gateway
  clusterIssuer: letsencrypt
  annotations:
    appgw.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: null

Use ingress.className for AGIC instead of the legacy kubernetes.io/ingress.class annotation. Kubernetes rejects manifests when both values are set and do not match exactly. null removes an inherited default annotation, which is useful when switching away from nginx and avoiding nginx-specific annotations on non-nginx controllers.

Additional CA Certificates

BaSyx services sometimes need to call HTTPS endpoints that use private CAs. Add those CAs with internal.CACertificates.trustStore.

Inline certificate bundle:

internal:
  CACertificates:
    trustStore:
      additionalCACertificates: |-
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----

Chart-local certificate directory:

instanceName: custom

internal:
  CACertificates:
    trustStore:
      chartFileDirectory: /config-files/certs/{{ .Values.instanceName }}

Expected directory inside the chart:

charts/basyx/config-files/certs/custom/
  partner-root-ca.crt
  external-service-ca.crt

The chart projects configured certificate sources into the pods and updates SSL_CERT_DIR automatically.

Database

By default, the chart creates a CloudNativePG cluster:

database:
  clusterName: basyx-database
  type: postgres
  database: basyx
  owner: basyx
  instances: 3
  storage:
    size: 10Gi

For smaller development deployments, reduce the number of database instances and storage size:

database:
  instances: 1
  storage:
    size: 5Gi

The values postgres, managed and cnpg all keep this managed database behavior for backwards compatibility.

Production CloudNativePG configuration

The chart can select storage classes independently for PGDATA and WAL, set PostgreSQL resources and parameters, and pass CloudNativePG scheduling rules through to the managed cluster. Start from values/values.postgres-production.example.yaml:

database:
  instances: 3
  storage:
    size: 100Gi
    storageClass: low-latency-block
  walStorage:
    enabled: true
    size: 20Gi
    storageClass: low-latency-block
  resources:
    requests:
      cpu: "2"
      memory: 8Gi
    limits:
      cpu: "2"
      memory: 8Gi
  affinity:
    enablePodAntiAffinity: true
    podAntiAffinityType: preferred
    topologyKey: topology.kubernetes.io/zone
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: ScheduleAnyway
      labelSelector: {}
      matchLabelKeys:
        - cnpg.io/cluster
  postgresql:
    parameters:
      max_connections: "300"
      shared_buffers: 2GB
      effective_cache_size: 6GB

Treat these values as a starting point, not universal production settings. Resource limits, PostgreSQL parameters, volume sizes, and topology rules must match the workload and the available nodes. The example uses equal CPU and memory requests and limits to give the PostgreSQL pods Guaranteed Kubernetes QoS. It uses preferred zone anti-affinity and ScheduleAnyway topology spreading so a labeled single-zone or capacity-constrained cluster can still schedule the database. matchLabelKeys derives the cluster label value from each incoming CloudNativePG pod instead of duplicating database.clusterName.

Before using the zone rules, verify that every eligible database node has a zone label:

kubectl get nodes -L topology.kubernetes.io/zone

Nodes without the configured topology key are not eligible for these pods. If the cluster does not use zone labels, change both database.affinity.topologyKey and database.topologySpreadConstraints[].topologyKey to kubernetes.io/hostname, or add consistent zone labels before deployment. Change either scheduling rule to a hard requirement only after verifying that enough eligible zones and nodes always exist.

Benchmark every candidate StorageClass on the target infrastructure before selecting it. CloudNativePG provides an fio command for its kubectl cnpg plugin. Run destructive storage benchmarks only in staging or pre-production:

kubectl cnpg fio basyx-storage-test \
  --namespace basyx-storage-test \
  --storageClass low-latency-block \
  --pvcSize 20Gi

Compare latency, IOPS, throughput, and sustained write behavior with a PostgreSQL-like block size. Also run workload-level tests such as pgbench or the BaSyx load test before changing production. See the CloudNativePG benchmarking guide.

The example intentionally leaves workload-dependent memory settings such as work_mem and maintenance_work_mem at their PostgreSQL defaults. work_mem can be allocated by several operations in one query and across concurrent connections. Set these parameters only after budgeting shared memory, connection overhead, autovacuum and maintenance memory, per-query memory, and operating-system headroom below the pod memory limit.

Enabling database.walStorage.enabled creates a dedicated WAL PVC for every instance. CloudNativePG does not support removing walStorage from an existing cluster after it has been enabled. Decide on the WAL layout before production rollout, include both PGDATA and WAL in capacity monitoring, backup, recovery, and instance-recreation procedures, and always treat an instance's PGDATA and WAL volumes as a pair.

Increasing storage.size or walStorage.size requires a StorageClass that supports volume expansion. Kubernetes PVCs cannot be shrunk. Changing storageClass in values does not migrate existing PVCs to new storage; moving a cluster requires a planned CloudNativePG migration or volume-recreation procedure. Follow the CloudNativePG 1.30 storage guidance before changing an existing cluster.

The chart fields in this section, including the nested affinity and label-selector structures, are validated against the stable postgresql.cnpg.io/v1 API documented for CloudNativePG 1.30. Confirm compatibility with the operator version installed in the target cluster during upgrades.

Render and review the production example before installing:

helm lint charts/basyx -f values/values.postgres-production.example.yaml
helm template basyx charts/basyx \
  -f values/values.postgres-production.example.yaml \
  > rendered-postgres-production.yaml

Existing PostgreSQL Database

To use an existing PostgreSQL database instead of deploying CloudNativePG, set database.type to external.

Recommended for production: point the chart to an existing Kubernetes Secret:

database:
  type: external
  existingSecret: basyx-external-postgres

The Secret must exist in the same namespace as the BaSyx release and contain these keys:

host
port
dbname
user
password
jdbc-uri

For BaSyx Go backend services, the Secret can additionally contain optional PostgreSQL keys:

sslmode
sslcert
sslkey
sslrootcert
connectTimeoutSeconds
applicationName
fallbackApplicationName
searchPath
options
timezone

These optional keys are rendered as optional POSTGRES_* environment variables. searchPath becomes POSTGRES_SEARCHPATH for BaSyx Go. For generated inline Secrets, it is also translated into the JDBC parameter currentSchema so Keycloak can use the same generated jdbc-uri.

Create such a Secret, for example:

kubectl -n basyx-custom create secret generic basyx-external-postgres \
  --from-literal=host='postgres.example.internal' \
  --from-literal=port='5432' \
  --from-literal=dbname='basyx' \
  --from-literal=user='basyx' \
  --from-literal=password='change-me' \
  --from-literal=jdbc-uri='jdbc:postgresql://postgres.example.internal:5432/basyx?sslmode=require&currentSchema=myschema' \
  --from-literal=sslmode='require' \
  --from-literal=searchPath='myschema'

Then install with the external database overlay:

helm upgrade --install basyx charts/basyx \
  -n basyx-custom \
  --create-namespace \
  -f values/values.example.yaml \
  -f values/values.external-db.example.yaml

For quick test deployments, the connection values can also be provided inline. In that case, the chart renders a Secret automatically:

database:
  type: external
  existingSecret: ""
  external:
    host: postgres.example.internal
    port: "5432"
    dbname: basyx
    user: basyx
    password: change-me
    sslmode: require
    searchPath: myschema

Inline values are easier to use, but less safe: the password is stored in the values file and in Helm release data. Do not commit real database passwords to Git. Use existingSecret for shared, production or GitOps-managed environments.

When the external database is independently managed and expected to be reachable before installing the chart, the Configuration Service database wait initContainer can be disabled:

configurationService:
  waitForDatabase:
    enabled: false

Service-Specific PostgreSQL Databases

The global database block is used by all BaSyx Go backend services by default. Individual services can override that connection when a deployment needs separate databases, for example when registries should use a shared core database while repositories use a namespace-local database.

Recommended for production: reference an existing Secret on the service:

database:
  type: postgres
  clusterName: basyx-company-database

aasRegistry:
  enabled: true
  database:
    existingSecret: basyx-core-postgres

aasDiscovery:
  enabled: true
  database:
    existingSecret: basyx-core-postgres

aasRepository:
  enabled: true
  # No override: uses the global database above.

The referenced Secret must contain the same required keys as the global external database Secret: host, port, dbname, user, password and jdbc-uri. It can also contain the optional PostgreSQL keys listed above, for example sslmode and searchPath.

For quick test deployments, a service can also define the connection inline. The chart renders a service-specific Secret automatically:

aasRegistry:
  enabled: true
  database:
    type: external
    host: postgres.example.internal
    port: "5432"
    dbname: basyx_registry
    user: basyx_registry
    password: change-me
    sslmode: require
    searchPath: registry_schema

Service-specific database overrides are runtime connections only. The built-in configurationService migrates the global database connection of the release. If a service points to a different external database, that database must already be initialized by another BaSyx release, by a separate Configuration Service run, or by an operational migration process.

For the Catena-X example, use the same overlay pattern:

helm upgrade --install basyx charts/basyx \
  -n basyx-catena-x \
  --create-namespace \
  -f values/values.catena-x.example.yaml \
  -f values/values.external-db.example.yaml

When global database.type: external is used, the chart does not render a CloudNativePG Cluster. The configured external database user must be allowed to create and migrate the BaSyx schema. The configurationService still runs by default and initializes or migrates the schema in the existing database. configurationService.waitForDatabase.enabled: false only disables the Configuration Service wait initContainer.

PostgreSQL connection pool budget

Every BaSyx Go pod owns an independent database/sql connection pool. Horizontal scaling therefore multiplies the possible database connections; replicas do not share a pool. The chart uses these common per-pod defaults:

environment:
  common:
    POSTGRES_MAXOPENCONNECTIONS: 50
    POSTGRES_MAXIDLECONNECTIONS: 25
    POSTGRES_CONNMAXLIFETIMEMINUTES: 5
    POSTGRES_CONNMAXIDLETIMEMINUTES: 0

Budget connections before increasing a service's replicaCount or autoscaling.maxReplicas. Use replicaCount as the maximum when autoscaling is disabled and autoscaling.maxReplicas when it is enabled. For all pods that connect to the same PostgreSQL primary, keep:

sum(BaSyx maximum replicas × POSTGRES_MAXOPENCONNECTIONS)
+ Keycloak and other application pools
+ temporary rolling-update pods
+ migration jobs
+ operational connections
< PostgreSQL max_connections

Calculate this separately for each PostgreSQL primary. Leave capacity for CloudNativePG management, monitoring, migrations, administrators, failover, and the maximum pod overlap allowed during rolling updates. Keycloak's database pool defaults to 100 connections per pod; when Keycloak shares the database, set an explicit limit under keycloak.environment.KC_DB_POOL_MAX_SIZE and include it in the calculation. For example:

keycloak:
  environment:
    KC_DB_POOL_MAX_SIZE: 20

Six BaSyx backend pods at the default maximum can request up to 300 connections before Keycloak or reserves are included. If PostgreSQL is configured for 300 connections, the per-pod pools must be reduced because no reserve remains. Set postgresql.parameters.max_connections only after checking database memory consumption and workload behavior; raising it is not a substitute for connection budgeting or a pooler.

POSTGRES_MAXIDLECONNECTIONS must not exceed the open limit. With the BaSyx Go pool configuration tracked in basyx-go-components#535 and implemented by PR #537, zero for max open, max idle, or maximum lifetime selects the application default of 50, 25, or 5 minutes respectively. When the explicit open limit is below 25 and idle is zero, the effective idle limit is capped to the open limit. Zero for POSTGRES_CONNMAXIDLETIMEMINUTES has different semantics: it disables recycling based on idle time. These settings and validation rules require BaSyx Go 1.0.5 or later.

Optional CloudNativePG read-write Pooler

For a managed CloudNativePG database, the chart can place a PgBouncer access layer between BaSyx runtime pods and the PostgreSQL primary:

database:
  pooler:
    enabled: true
    instances: 2
    poolMode: transaction
    maxClientConn: 1000
    defaultPoolSize: 25
    preparedStatements:
      enabled: true
      maxPreparedStatements: 100
    parameters:
      reserve_pool_size: "5"
    resources:
      requests:
        cpu: 250m
        memory: 256Mi
    nodeSelector: {}
    affinity: {}
    tolerations: []
    topologySpreadConstraints: []

The Pooler is disabled by default and is rendered only for a managed database. When enabled, BaSyx runtime services using the global database connect to <database.clusterName>-rw-pooler. They reuse the user, password, database and port from the CloudNativePG application Secret. Service-specific database overrides are not redirected. For long cluster names, the chart truncates the cluster-name portion of the Pooler service name to keep the generated name within 63 characters and distinct from the CloudNativePG Cluster name.

The Configuration Service wait container and migration container always use the direct CloudNativePG read-write service from the application Secret. Migrations use session-level advisory locks and must not run through a transaction Pooler. Keycloak also continues to use the direct JDBC URI.

maxClientConn is the maximum number of client connections accepted by each PgBouncer pod. defaultPoolSize is the default number of PostgreSQL server connections maintained by each PgBouncer pod for each user/database pair. A useful starting budget for the primary is:

Pooler instances × (defaultPoolSize + reserve_pool_size)
+ direct Keycloak connections
+ migration, monitoring, administration and failover reserve
< PostgreSQL max_connections

Adjust the formula when multiple user/database pairs use the Pooler or when additional PgBouncer limits are configured. The BaSyx per-pod POSTGRES_MAXOPENCONNECTIONS values still bound client connections into PgBouncer. They may collectively exceed the PgBouncer server pool because PgBouncer queues work, but they must remain below the aggregate client capacity and within acceptable queueing latency:

sum(BaSyx maximum replicas × POSTGRES_MAXOPENCONNECTIONS)
< Pooler instances × maxClientConn

This is an absolute aggregate ceiling, not a production target. Client connections are persistent and can be distributed unevenly across Pooler pods. For deployments that must tolerate one Pooler restart or failure, budget against (Pooler instances - 1) × maxClientConn and leave additional headroom for distribution skew. A single Pooler instance has no client-capacity redundancy.

A Pooler controls connection concurrency; it does not add write capacity to the single PostgreSQL primary. If latency rises while PgBouncer clients wait for server connections, first inspect query time, lock contention, storage latency and primary CPU before increasing defaultPoolSize.

Transaction pooling requires PgBouncer prepared-statement tracking for clients that use protocol-level prepared statements. The chart enables max_prepared_statements with a value of 100 by default when the Pooler is enabled. Set preparedStatements.enabled: false only for a verified workload that does not use them. Additional values under parameters must be strings and must be supported by the PgBouncer version bundled with the installed CloudNativePG operator. The first-class connection limits and prepared statement settings take precedence over duplicate parameter keys.

Before production rollout, verify the exact CloudNativePG and PgBouncer versions, render the Pooler resource, and run the BaSyx integration and load tests through the Pooler. Include bulk endpoints, large-object operations, rolling updates, primary switchovers and connection saturation. Watch cnpg_pgbouncer_* metrics together with the BaSyx PostgreSQL pool metrics to distinguish application-pool waits from PgBouncer queueing and PostgreSQL execution time.

PostgreSQL reader routing and read-only Pooler

BaSyx Go 1.0.7 can route eligible reads through a separate PostgreSQL connection. Chart 3.9.0 exposes this as database.reader and keeps it disabled by default. Without reader configuration, no POSTGRES_READER_* variables are rendered and every service continues to reuse its writer pool.

For a managed CloudNativePG database, enabling the reader routes reads to the native <database.clusterName>-ro Service. When using this native endpoint, the cluster must have at least two instances:

database:
  instances: 3
  reader:
    enabled: true
    maxOpenConnections: 50
    maxIdleConnections: 25
    connMaxLifetimeMinutes: 5
    connMaxIdleTimeMinutes: 0

The optional read-only Pooler uses the same PgBouncer, resource and scheduling settings as the read-write Pooler:

database:
  instances: 3
  reader:
    enabled: true
    pooler:
      enabled: true
      instances: 2
      poolMode: transaction
      maxClientConn: 1000
      defaultPoolSize: 25
      preparedStatements:
        enabled: true
        maxPreparedStatements: 100

This creates a CloudNativePG Pooler of type ro named <database.clusterName>-ro-pooler and uses its Service as the reader host. Writer traffic remains on the direct read-write Service or the separately configured database.pooler.

For an external PostgreSQL deployment, reference a complete reader Secret:

database:
  type: external
  existingSecret: basyx-postgres-writer
  reader:
    enabled: true
    existingSecret: basyx-postgres-reader

The reader Secret must contain host, port, dbname, user and password. It can also contain the optional PostgreSQL keys accepted by the writer Secret. Alternatively, configure only a separate endpoint and reuse the effective writer Secret for omitted fields:

database:
  type: external
  existingSecret: basyx-postgres-writer
  reader:
    enabled: true
    host: postgres-reader.example.internal
    sslmode: verify-full

Inline reader connection values are stored in a generated Secret. Prefer existing Secrets, TLS with server verification and a least-privilege read-only user in production. For endpoints that do not enforce read-only transactions, consider options: "-c default_transaction_read_only=on" as an additional safeguard after verifying provider compatibility. A managed writer with one instance can use an explicit external reader host or existingSecret. Setting only reader.enabled: true for an external database deliberately reuses the complete writer endpoint; it is compatible with the reader routing feature but does not add database capacity.

Each charted BaSyx Go HTTP service supports a complete local reader replacement at <component>.database.reader: AAS and Submodel Registry, Discovery Service, Digital Twin Registry, AAS and Submodel Repository, Concept Description Repository, AAS Environment, Company Lookup Service and DPP API. A reader-only local override retains the global writer. A service-local writer override without a local reader disables global reader inheritance so that the service cannot accidentally query another database. The Configuration Service and Keycloak remain writer-only. BaSyx Go also supports reader routing in the AASX File Server, but that service is not currently deployed by this chart.

Reader results are eventually consistent. Replication lag can briefly expose the preceding state after writes, deletions and authorization-relevant attribute changes. Mutation, transaction, guard and read-after-write work continues to use the writer. Deployments requiring immediate revocation or read-after-write consistency must leave the affected service on the writer or use replication guarantees that meet their consistency window. Reader startup fails if the configured endpoint is unavailable; requests are not silently routed back to the writer.

Budget the four reader pool values independently for every BaSyx pod and destination standby. Aggregate BaSyx clients must fit the capacity of the surviving read-only Pooler pods. PostgreSQL server-connection budgeting must include every active read-only Pooler pod, every user/database pool, and distribution or failover across the standbys. Monitor replication lag together with BaSyx db.client.connections.* and CloudNativePG cnpg_pgbouncer_* metrics before increasing limits. Start from values/values.read-replica.example.yaml.

Database and Pooler PodMonitors

The chart can create Prometheus Operator PodMonitor resources for the managed CloudNativePG instances, the read-write Pooler and the read-only Pooler. All are disabled by default, and the chart does not install Prometheus Operator or the monitoring.coreos.com CRDs.

database:
  monitoring:
    enabled: true
    # Add labels required by the Prometheus podMonitorSelector.
    labels:
      release: kube-prometheus-stack
    annotations: {}
    interval: 30s
    scrapeTimeout: 10s
    # Empty selects pods in the Helm release namespace.
    namespaceSelector: {}
    relabelings: []
    metricRelabelings:
      - sourceLabels:
          - __name__
        regex: go_.*
        action: drop
  pooler:
    enabled: true
    monitoring:
      enabled: true
      labels:
        release: kube-prometheus-stack
      annotations: {}
      interval: 30s
      scrapeTimeout: 10s
      namespaceSelector: {}
      relabelings: []
      metricRelabelings: []
  reader:
    enabled: true
    pooler:
      enabled: true
      monitoring:
        enabled: true
        labels:
          release: kube-prometheus-stack
        annotations: {}
        interval: 30s
        scrapeTimeout: 10s
        namespaceSelector: {}
        relabelings: []
        metricRelabelings: []

PostgreSQL and Pooler monitoring can be enabled independently. The PostgreSQL monitor is rendered only for a managed database. Each Pooler monitor is rendered only when its corresponding managed Pooler is enabled. None of these monitors is rendered for database.type: external; monitoring an external database remains the responsibility of that database's platform.

The chart selects PostgreSQL pods through cnpg.io/cluster and Pooler pods through cnpg.io/poolerName. Both exporters are scraped through the named metrics port at /metrics. An empty namespaceSelector keeps target discovery in the PodMonitor's namespace. If matchNames or any is configured, make sure it cannot select an unrelated CloudNativePG resource with the same name in another namespace.

Prometheus must be configured to discover PodMonitor resources in the Helm release namespace and to match the configured metadata labels. annotations are applied to the PodMonitor itself. relabelings modify discovered targets, while metricRelabelings modify or discard samples before ingestion. Review metric relabeling rules carefully because they can silently remove metrics. Prometheus Operator rejects a scrapeTimeout greater than the interval.

Useful starting points for dashboards and alerts include:

Area Metrics
PostgreSQL availability cnpg_collector_up, cnpg_collector_last_collection_error
PostgreSQL connection pressure cnpg_backends_total, cnpg_backends_waiting_total, cnpg_backends_max_tx_duration_seconds
WAL and archiving cnpg_collector_pg_wal, cnpg_collector_pg_wal_archive_status, cnpg_collector_wal_bytes, cnpg_collector_wal_write_time, cnpg_collector_wal_sync_time
Replication cnpg_pg_replication_lag, cnpg_pg_replication_is_wal_receiver_up, cnpg_pg_replication_streaming_replicas, cnpg_collector_sync_replicas
Database size cnpg_pg_database_size_bytes
Pooler pressure cnpg_pgbouncer_pools_cl_active, cnpg_pgbouncer_pools_cl_waiting, cnpg_pgbouncer_pools_maxwait
Pooler server use cnpg_pgbouncer_pools_sv_active, cnpg_pgbouncer_pools_sv_idle
Pooler throughput and waits cnpg_pgbouncer_stats_total_query_count, cnpg_pgbouncer_stats_total_xact_count, cnpg_pgbouncer_stats_total_wait_time

Some PostgreSQL query metrics depend on the default monitoring queries shipped with the installed CloudNativePG version. For storage capacity and latency, combine these database metrics with Kubernetes PVC and node/storage metrics such as kubelet_volume_stats_available_bytes; those are not emitted by the CloudNativePG PodMonitor.

BaSyx Configuration Service

BaSyx Go requires the database schema to be prepared before DB-backed services start. The chart enables configurationService by default for this. It renders a Kubernetes Job using eclipsebasyx/basyxconfigurationservice-go and the same PostgreSQL Secret as the runtime services.

The Configuration Service image is versioned independently from the BaSyx runtime service images. Set configurationService.image.tag or, preferably for reproducible deployments, configurationService.image.digest explicitly when a schema migration image changes.

The default job runs as a Helm hook:

configurationService:
  enabled: true
  image:
    tag: "1.0.2"
  waitForDatabase:
    enabled: true
  hook:
    enabled: true
    events:
      - post-install
      - pre-upgrade

pre-upgrade runs schema migrations before updated runtime pods are rolled out. post-install is used for fresh installs because the PostgreSQL database must exist before the job can connect.

When the chart is deployed by Argo CD, enable the optional Argo CD sync annotations. Argo CD uses helm template and maps Helm hooks into its own sync phases, so the chart can explicitly run the Configuration Service as a Sync hook in wave 10 and the runtime Deployments in wave 20. This lets Argo CD recreate prerequisites such as the ServiceAccount, database resources, and generated PostgreSQL Secret before starting the schema job, while still keeping runtime pods behind the migration. These annotations are disabled by default and are not needed for plain Helm installations.

argocd:
  enabled: true
  runtimeSyncWave: "20"

configurationService:
  hook:
    argocd:
      enabled: true
      hook: Sync
      syncWave: "10"
      deletePolicy:
        - BeforeHookCreation

On fresh installs, PostgreSQL may need some time before it accepts connections. The chart therefore adds a wait-for-database init container that polls PostgreSQL with pg_isready before starting the Configuration Service. This avoids slow Kubernetes Job backoff loops when the database is simply not ready yet.

After deployment, verify the job and logs with:

kubectl -n basyx-custom get job basyx-configuration
kubectl -n basyx-custom logs job/basyx-configuration

The successful database state is stored in the basyxsystem table with state=clean.

Keycloak

Keycloak is disabled in the unsecured example and enabled in values/values.secured.example.yaml. When enabling it, configure at least admin and client credentials:

keycloak:
  enabled: true
  realm: basyx
  secrets:
    admin:
      username: admin
      password: change-me
    client:
      name: basyx-ui
      clientPassword: change-me

The chart can also create roles, clients, protocol mappers and users through keycloak.initialization.*. The default chart values initialize a generic admin user named basyx.admin with the password changeit. Override keycloak.initialization.users and keycloak.secrets.* before using Keycloak in any shared or production environment.

BaSyx Services

Each backend service has a similar values structure:

aasRepository:
  enabled: true
  replicaCount: 1
  image:
    repository: eclipsebasyx/aasrepository-go
    tag: "1.0.9"
    pullPolicy: IfNotPresent
  service:
    type: ClusterIP
    port: "8080"
  ingress:
    enabled: true

Supported service blocks:

  • aasDiscovery
  • aasRegistry
  • aasRepository
  • aasEnvironment
  • dppApi
  • submodelRegistry
  • submodelRepository
  • cdRepository
  • companyLookup
  • digitalTwinRegistry

Most service blocks support enabled, replicaCount, autoscaling, image.*, imagePullSecrets, service.*, ingress.*, resources, nodeSelector, tolerations, affinity, topologySpreadConstraints, podAnnotations, podLabels, podSecurityContext, securityContext, volumes, volumeMounts and optional service-local server, history, eventing, general and abac overrides.

Use topology spread constraints to distribute replicas across nodes or zones. A dedicated pod label keeps the selector independent of the Helm release name:

submodelRepository:
  replicaCount: 3
  podLabels:
    basyx.eclipse.org/topology-group: submodel-repository
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: kubernetes.io/hostname
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          basyx.eclipse.org/topology-group: submodel-repository
      matchLabelKeys:
        - pod-template-hash

The selector must match the labels of the service's own pods. Use DoNotSchedule only after verifying that enough eligible topology domains and capacity are available, otherwise replicas can remain pending. matchLabelKeys requires Kubernetes 1.27 or later, and minDomains can require feature-gate support before Kubernetes 1.30.

aasEnvironment uses the eclipsebasyx/aasenvironment-go image and defaults to service port 8082. It can be deployed as a single BaSyx Go API endpoint backed by the chart's PostgreSQL database and Configuration Service. It enables AAS Registry, Submodel Registry and Discovery integration by default:

aasEnvironment:
  enabled: true
  environment:
    GENERAL_AASREGISTRYINTEGRATION: true
    GENERAL_SUBMODELREGISTRYINTEGRATION: true
    GENERAL_DISCOVERYINTEGRATION: true

To import preconfigured AAS files, mount the files into the pod and point BaSyx Go to the mount path:

aasEnvironment:
  enabled: true
  general:
    aasPreconfigPaths:
      - /app/preconfiguration
  volumeMounts:
    - name: aas-preconfiguration
      mountPath: /app/preconfiguration
      readOnly: true
  volumes:
    - name: aas-preconfiguration
      configMap:
        name: aas-preconfiguration

dppApi uses the eclipsebasyx/dppapi-go image and defaults to service port 8080. It stores DPP data in the shared BaSyx database and defaults to audit history settings matching the BaSyx DPP API Docker Compose example:

dppApi:
  enabled: true
  history:
    mode: audit
    auditIdentityMode: extended

For a DPP-oriented setup similar to the BaSyx DPP API Docker Compose example, enable DPP API together with AAS Environment and Web UI:

dppApi:
  enabled: true

aasEnvironment:
  enabled: true

aasWebGui:
  enabled: true
  infrastructureConfig:
    infrastructures:
      default: dpp
      dpp:
        name: "DPP Shared AAS Environment"
        components:
          aasDiscovery:
            baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasEnvironment }}/lookup/shells"
          aasRegistry:
            baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasEnvironment }}/shell-descriptors"
            hasDiscoveryIntegration: true
          submodelRegistry:
            baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasEnvironment }}/submodel-descriptors"
          aasRepository:
            baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasEnvironment }}/shells"
            hasRegistryIntegration: true
          submodelRepository:
            baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasEnvironment }}/submodels"
            hasRegistryIntegration: true
          conceptDescriptionRepository:
            baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasEnvironment }}/concept-descriptions"
        security:
          type: None
          config: null

Horizontal Pod Autoscaling

Every BaSyx Go backend listed above can use an autoscaling/v2 HorizontalPodAutoscaler. Autoscaling is disabled by default. When it is enabled, the chart omits spec.replicas from the Deployment so that Helm does not reset the replica count managed by the HPA.

The HPA uses the Kubernetes resource metrics API. Install and verify a metrics pipeline such as Metrics Server before enabling it:

kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes
kubectl top pods

CPU utilization requires a CPU request. Enabling a service HPA without resources.requests.cpu fails chart validation. A memory request is also required when targetMemoryUtilizationPercentage is configured.

digitalTwinRegistry:
  enabled: true
  resources:
    requests:
      cpu: 500m
      memory: 512Mi
    limits:
      memory: 2Gi
  autoscaling:
    enabled: true
    minReplicas: 2
    maxReplicas: 6
    targetCPUUtilizationPercentage: 70
    targetMemoryUtilizationPercentage: 75
    behavior:
      scaleUp:
        stabilizationWindowSeconds: 0
        selectPolicy: Max
        policies:
          - type: Percent
            value: 100
            periodSeconds: 60
      scaleDown:
        stabilizationWindowSeconds: 300

behavior.scaleUp and behavior.scaleDown accept Kubernetes HPA stabilizationWindowSeconds, selectPolicy, and policies. Leave behavior empty to use Kubernetes defaults. The static replicaCount remains the Deployment replica count only while autoscaling is disabled.

Set maxReplicas from a measured service and database budget, not only from available Kubernetes CPU. For a service that connects directly to PostgreSQL, keep:

(maxReplicas + rollout surge pods) × POSTGRES_MAXOPENCONNECTIONS
<= that service's writer connection budget

The shared Deployments use Kubernetes' default rolling-update strategy, which can create ceil(maxReplicas × 25%) surge pods. Include that overlap unless a post-renderer or another deployment policy sets a different maxSurge. This is the service-specific part of the complete PostgreSQL budget described above. Calculate the same limit independently for POSTGRES_READER_MAXOPENCONNECTIONS when reader routing is enabled. When PgBouncer is used, the HPA maximum must also fit the surviving Pooler client capacity with headroom for connection distribution, and the Pooler server pools must stay within the PostgreSQL backend budget. PgBouncer queues connections but does not add write capacity to the primary.

Do not use HPA to react to PostgreSQL latency or an exhausted connection pool: adding application pods during a database incident can increase pressure. Load-test scale-up, scale-down, rolling updates, standby loss, and the maximum replica count before production rollout. Start from values/values.autoscaling.example.yaml.

BaSyx Runtime Configuration

BaSyx Go runtime options can be configured globally and overridden per backend service. Global values are rendered into the shared <fullname>-common-config Secret, for example basyx-common-config when installing the chart as release basyx, and are loaded by all backend services. Service-local values are rendered as explicit container environment variables and therefore override the global defaults.

This applies to:

  • aasDiscovery
  • aasRegistry
  • aasRepository
  • aasEnvironment
  • dppApi
  • submodelRegistry
  • submodelRepository
  • cdRepository
  • companyLookup
  • digitalTwinRegistry

Logging, Request Correlation And Telemetry

Structured logging and tracing require BaSyx Go 1.0.4 or newer. PostgreSQL pool metrics require BaSyx Go 1.0.6 or newer.

Logging is configured for every BaSyx Go backend and the Configuration Service:

logging:
  format: json
  level: info

BaSyx writes logs to standard error. The chart does not install a log agent or send logs directly to Loki. Collect container logs through the cluster logging pipeline.

HTTP services automatically return X-Request-ID and X-Correlation-ID and emit one structured access record for each request. No chart value is required for request correlation, and these IDs must not be treated as authenticated identity data.

Tracing and metrics are independently disabled by default. To send both signals to an existing OpenTelemetry Collector:

telemetry:
  tracesExporter: otlp
  metricsExporter: otlp
  endpoint: http://opentelemetry-collector.observability.svc.cluster.local:4318
  protocol: http/protobuf
  metricsExportInterval: "60000"
  metricsExportTimeout: "30000"
  existingSecret: ""
  resourceAttributes: deployment.environment.name=production
  tracesSampler: parentbased_traceidratio
  tracesSamplerArg: "0.1"
  propagators:
    - tracecontext
    - baggage

metricsExportInterval and metricsExportTimeout are optional positive millisecond values. Leave them empty to use the OpenTelemetry SDK defaults. The shared endpoint and protocol apply to traces and metrics. Advanced standard settings, including signal-specific endpoints, headers, compression and timeouts, can be supplied through environment.common, telemetry.existingSecret, or a service's environment map.

BaSyx Go exports the following PostgreSQL writer-pool metrics without executing additional database queries:

Metric Meaning
db.client.connection.max Configured maximum open connections
db.client.connection.count with state=used or state=idle Current pool use
basyx.db.client.connection.waits Cumulative waits for a connection
basyx.db.client.connection.wait_time Cumulative wait duration in seconds
basyx.db.client.connection.closed with a bounded reason Cumulative closures caused by pool limits

Use rates for cumulative counters. A rising wait rate while used connections approach the maximum points to the BaSyx application pool. When the optional CloudNativePG Pooler is enabled, compare these metrics with cnpg_pgbouncer_* metrics to distinguish waits inside the application from PgBouncer queueing.

The Configuration Service has no HTTP server and remains logging-only.

Do not put OTLP authorization headers into a values file. Create a Kubernetes Secret containing the standard environment variable and reference it instead:

kubectl -n basyx create secret generic basyx-otel-credentials \
  --from-literal=OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer%20<token>'
telemetry:
  existingSecret: basyx-otel-credentials

Existing Secret changes do not alter the Deployment template automatically. Restart the BaSyx backend pods after rotating telemetry credentials.

The example overlay can be combined with another values file:

helm upgrade --install basyx charts/basyx \
  -n basyx \
  -f values/values.minimal.yaml \
  -f values/values.observability.example.yaml

This overlay connects BaSyx services to an existing Collector. The chart intentionally does not deploy Grafana, Loki, Tempo, Jaeger, Alloy, or the Collector because their authentication, storage, retention, tenancy, and resource requirements are cluster-specific.

Global runtime defaults:

general:
  trustProxyHeaders: false
  trustedProxyCIDRs: []
  bulkBatchLimit: 1000
  aasPreconfigPaths: []

server:
  readHeaderTimeoutSeconds: 15
  readTimeoutSeconds: 300
  writeTimeoutSeconds: 300
  idleTimeoutSeconds: 60
  shutdownTimeoutSeconds: 10

history:
  mode: "off"
  immutability: "none"
  auditIdentityMode: "none"
  evidence:
    enabled: false
    provider: "none"

eventing:
  enabled: false
  topicPrefix: basyx

abac:
  policyFileImport: ""
  policyScope: ""
  managementApi:
    enabled: false

Service-local override example:

history:
  mode: "off"

aasRepository:
  server:
    readTimeoutSeconds: 600
    writeTimeoutSeconds: 600
    shutdownTimeoutSeconds: 30
  history:
    mode: audit
    immutability: postgres_guarded
    auditIdentityMode: extended
    evidence:
      enabled: true
      provider: s3
      bucket: basyx-history-evidence
      endpoint: http://minio:9000
      pathStyle: true
  eventing:
    topicPrefix: aas-repository-events
  general:
    bulkBatchLimit: 500
    aasPreconfigPaths:
      - /aas/preconfigured
  abac:
    policyFileImport: if_missing
    policyScope: aas-repository
    managementApi:
      enabled: true

History settings can be overridden independently for each backend service. Use the global history block as the default for all services, then add a service-local history block where a component needs different history or audit behavior:

history:
  mode: "off"

aasRepository:
  history:
    mode: api
    fullSnapshotInterval: 1

submodelRepository:
  history:
    mode: audit
    immutability: postgres_guarded
    auditIdentityMode: extended

In this example, history stays disabled globally, the AAS Repository records API history, and the Submodel Repository uses audit-oriented history settings. The same pattern can be used for aasDiscovery, aasRegistry, aasEnvironment, dppApi, submodelRegistry, cdRepository, companyLookup and digitalTwinRegistry.

If you need a parameter that is not modeled as a structured value yet, use the raw environment maps:

environment:
  common:
    BASYX_HISTORY_MODE: api

aasRepository:
  environment:
    BASYX_HISTORY_MODE: audit

Raw environment maps are the escape hatch and take precedence over structured values. In the example above, aasRepository.environment.BASYX_HISTORY_MODE wins over aasRepository.history.mode.

General Runtime Values

Value Rendered environment variable Description
general.enableImplicitCasts GENERAL_ENABLEIMPLICITCASTS Enables implicit value casts.
general.enableDescriptorDebug GENERAL_ENABLEDESCRIPTORDEBUG Enables descriptor debug behavior.
general.discoveryIntegration GENERAL_DISCOVERYINTEGRATION Enables AAS Discovery integration where supported.
general.enableCustomMiddlewareHeaderInjection GENERAL_ENABLECUSTOMMIDDLEWAREHEADERINJECTION Enables custom middleware header injection.
general.supportsSingularSupplementalSemanticId GENERAL_SUPPORTSSINGULARSUPPLEMENTALSEMANTICID Enables compatibility for singular supplemental semantic IDs.
general.aasRegistryIntegration GENERAL_AASREGISTRYINTEGRATION Enables AAS Registry synchronization.
general.submodelRegistryIntegration GENERAL_SUBMODELREGISTRYINTEGRATION Enables Submodel Registry synchronization.
general.externalUrl GENERAL_EXTERNALURL Public external URL used by registry synchronization.
general.trustProxyHeaders GENERAL_TRUSTPROXYHEADERS Trusts forwarded proxy headers. Only enable behind trusted reverse proxies.
general.trustedProxyCIDRs GENERAL_TRUSTEDPROXYCIDRS Comma-separated trusted proxy CIDR list.
general.uploadMaxSizeBytes GENERAL_UPLOADMAXSIZEBYTES Maximum compressed HTTP request size, including multipart overhead, in bytes. Must be greater than 0.
general.aasxMaxPartCount GENERAL_AASXMAXPARTCOUNT Maximum number of non-directory entries in an AASX package.
general.aasxMaxOPCMetadataSizeBytes GENERAL_AASXMAXOPCMETADATASIZEBYTES Maximum combined expanded size of AASX OPC metadata.
general.aasxMaxPartExpandedSizeBytes GENERAL_AASXMAXPARTEXPANDEDSIZEBYTES Maximum expanded size of one AASX payload part.
general.aasxMaxTotalExpandedSizeBytes GENERAL_AASXMAXTOTALEXPANDEDSIZEBYTES Maximum combined expanded size of all AASX payload parts.
general.aasxMaxThumbnailSizeBytes GENERAL_AASXMAXTHUMBNAILSIZEBYTES Maximum expanded size of an AASX thumbnail.
general.bulkBatchLimit GENERAL_BULK_BATCH_LIMIT Maximum row count per generated bulk SQL statement. Must be greater than 0.
general.aasPreconfigPaths GENERAL_AAS_PRECONFIG_PATHS Comma-separated paths for preconfigured AAS input. Mount matching files or directories with service-specific volumes and volumeMounts.

aasRepository and submodelRepository already set registry integration related values in their service-local environment maps by default. Override these service-local values only when you intentionally want different registry synchronization behavior.

Server Runtime Values

The server block controls BaSyx Go HTTP server timeouts. All timeout values are configured in seconds and must be greater than 0.

Value Rendered environment variable Default Description
server.readHeaderTimeoutSeconds SERVER_READ_HEADER_TIMEOUT_SECONDS 15 Maximum time to read HTTP request headers.
server.readTimeoutSeconds SERVER_READ_TIMEOUT_SECONDS 300 Maximum time to read a full HTTP request including the body.
server.writeTimeoutSeconds SERVER_WRITE_TIMEOUT_SECONDS 300 Maximum time to write an HTTP response.
server.idleTimeoutSeconds SERVER_IDLE_TIMEOUT_SECONDS 60 Maximum keep-alive idle time before waiting for the next request ends.
server.shutdownTimeoutSeconds SERVER_SHUTDOWN_TIMEOUT_SECONDS 10 Maximum graceful shutdown time for in-flight requests after the service receives a termination signal.

As with general, history, eventing and abac, these values can be set globally or overridden per backend service:

server:
  readTimeoutSeconds: 300

aasRepository:
  server:
    readTimeoutSeconds: 900

History, Audit And Evidence Values

Value Rendered environment variable Description
history.mode BASYX_HISTORY_MODE History mode. Typical values are off, api or audit.
history.retentionDays BASYX_HISTORY_RETENTION_DAYS Retention period in days. 0 keeps the service default.
history.fullSnapshotInterval BASYX_HISTORY_FULL_SNAPSHOT_INTERVAL Interval for full history snapshots.
history.immutability BASYX_HISTORY_IMMUTABILITY Immutability mode, e.g. none, postgres_guarded or external_anchor.
history.auditIdentityMode BASYX_AUDIT_IDENTITY_MODE Audit identity mode, e.g. none, minimal or extended.
history.evidence.enabled BASYX_HISTORY_EVIDENCE_ENABLED Enables external evidence writing.
history.evidence.provider BASYX_HISTORY_EVIDENCE_PROVIDER Evidence provider, e.g. none or s3.
history.evidence.bucket BASYX_HISTORY_EVIDENCE_BUCKET Evidence storage bucket.
history.evidence.prefix BASYX_HISTORY_EVIDENCE_PREFIX Object prefix for evidence storage.
history.evidence.region BASYX_HISTORY_EVIDENCE_REGION S3-compatible region.
history.evidence.endpoint BASYX_HISTORY_EVIDENCE_ENDPOINT S3-compatible endpoint URL.
history.evidence.accessKeyId BASYX_HISTORY_EVIDENCE_ACCESS_KEY_ID Evidence storage access key ID. Prefer external secret handling for real credentials.
history.evidence.secretAccessKey BASYX_HISTORY_EVIDENCE_SECRET_ACCESS_KEY Evidence storage secret access key. Prefer external secret handling for real credentials.
history.evidence.pathStyle BASYX_HISTORY_EVIDENCE_PATH_STYLE Enables path-style S3 access.
history.evidence.retentionMode BASYX_HISTORY_EVIDENCE_RETENTION_MODE Object lock retention mode, if supported by the backend.
history.evidence.retentionDays BASYX_HISTORY_EVIDENCE_RETENTION_DAYS Object lock retention period in days.
history.evidence.writeTimeoutSeconds BASYX_HISTORY_EVIDENCE_WRITE_TIMEOUT_SECONDS Evidence write timeout in seconds.
history.evidence.signing.privateKeyPath BASYX_HISTORY_EVIDENCE_SIGNING_PRIVATE_KEY_PATH Private key path for evidence signing. Mount the key separately.
history.evidence.signing.publicKeyPath BASYX_HISTORY_EVIDENCE_SIGNING_PUBLIC_KEY_PATH Public key path for evidence verification. Mount the key separately.
history.evidence.signing.required BASYX_HISTORY_EVIDENCE_SIGNING_REQUIRED Requires signing for evidence records.
history.integrityAnchor.provider BASYX_HISTORY_INTEGRITY_ANCHOR_PROVIDER Integrity anchor provider.

Eventing Values

Value Rendered environment variable Description
eventing.enabled BASYX_EVENTING_ENABLED Enables event publishing.
eventing.format BASYX_EVENTING_FORMAT Event payload format, default cloudevents.
eventing.sinks BASYX_EVENTING_SINKS Comma-separated sink list.
eventing.outboxEnabled BASYX_EVENTING_OUTBOX_ENABLED Enables outbox processing.
eventing.topicPrefix BASYX_EVENTING_TOPIC_PREFIX Event topic prefix.

The current BaSyx Go implementation may fail fast when event publishing or outbox processing is enabled before a matching implementation is available. Keep eventing.enabled: false unless you intentionally deploy a compatible eventing setup.

ABAC Runtime Values

Value Rendered environment variable Description
abac.policyFileImport ABAC_POLICY_FILE_IMPORT Controls startup import behavior for ABAC policy files, e.g. always, if_missing or never. Empty value keeps the service default.
abac.policyScope ABAC_POLICY_SCOPE Optional database namespace for stored ABAC policies. Empty value keeps the service default scope. Use different scopes to isolate deployments that share a database.
abac.managementApi.enabled ABAC_MANAGEMENT_API_ENABLED Enables the ABAC management API where supported.

AAS Web UI

Enable the Web UI with:

aasWebGui:
  enabled: true
  image:
    tag: v2-260801

The Web UI infrastructure is rendered from aasWebGui.infrastructureConfig. The defaults derive service URLs from host and paths.*.

Logo files are read from the chart-local config-files/logos directory.

ABAC Authorization

ABAC is controlled globally with:

abac:
  enabled: true

The default rule grants full access to users with token claim role=admin.

You can override rules globally:

abac:
  accessRules: |
    {
      "AllAccessPermissionRules": {
        "DEFATTRIBUTES": [],
        "DEFOBJECTS": [],
        "DEFACLS": [],
        "DEFFORMULAS": [],
        "rules": []
      }
    }

Or per service:

aasRepository:
  abac:
    enabled: true
    accessRules: |
      { }
    trustList: |
      [ ]

The default trust list is derived from host, paths.keycloak, keycloak.realm and environment.common.OIDC_AUDIENCE.

Network Policies

Network Policies are not included in this chart. For production deployments, restrict east-west traffic between pods by deploying NetworkPolicy resources separately. Your cluster must have a Network Policy controller installed (e.g. Calico, Cilium, or Weave). Standard managed Kubernetes offerings (GKE, EKS, AKS) support this natively.

Recommended rules:

  • Allow ingress controller → all BaSyx services (HTTP)
  • Allow all BaSyx services → PostgreSQL (port 5432)
  • Allow all BaSyx services → Keycloak (port 8080, when enabled)
  • Deny all other ingress by default

Example policy restricting ingress to the AAS Registry:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-to-aas-registry
  namespace: basyx-custom
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: aas-registry
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - port: 8080

Development And Testing

Install the Helm unittest plugin once:

helm plugin install https://github.com/helm-unittest/helm-unittest --verify=false

Run local chart checks:

helm lint charts/basyx
helm unittest charts/basyx

Run lint with custom values:

helm lint charts/basyx -f values/values.example.yaml
helm lint charts/basyx -f values/values.minimal.yaml
helm lint charts/basyx -f values/values.secured.example.yaml
helm lint charts/basyx -f values/values.catena-x.example.yaml

Runtime smoke tests after deployment:

helm test basyx -n <namespace> --logs

Debugging

Many BaSyx images are intentionally slim. Prefer ephemeral debug containers:

kubectl -n <namespace> debug -it pod/<pod-name> \
  --image=nicolaka/netshoot \
  --target=<container-name>

Troubleshooting

Symptom What to check
no matches for kind "Certificate" cert-manager CRDs are missing. Install cert-manager with CRDs enabled.
no matches for kind "Cluster" in version "postgresql.cnpg.io/v1" CloudNativePG CRDs are missing. Install CloudNativePG.
Ingress returns 404 Check host, paths.*, ingress class and whether the service itself has a route for that path.
Pods cannot verify Keycloak TLS Check the internal CA secret, custom CA mounts and SSL_CERT_DIR.
Token verification failed: expected audience ... Check Keycloak protocol mappers and environment.common.OIDC_AUDIENCE.
ABAC(model): NO_MATCH Check token claims, ABAC object definitions, route patterns and whether pods rolled after config changes.
Custom CA not visible in pod Run helm test <release> -n <namespace> --logs and inspect /etc/ssl/certs/custom.
Upgrade targets the wrong cluster Pass --kube-context <context> explicitly.

Repository Layout

charts/basyx/                       Helm chart
charts/basyx/Chart.yaml             Chart metadata
charts/basyx/values.yaml            Default chart values
charts/basyx/templates/             Kubernetes templates
charts/basyx/tests/                 helm-unittest suites
charts/basyx/config-files/          Chart-local files such as logos and optional certs
examples/postman/                   Postman collections for example setups
values/                             Custom values overlays

Open Source Notes

Before publishing publicly:

  • Keep real deployment values, passwords, client secrets and private certificates out of the public repository.
  • Provide sanitized example values instead of production overlays.
  • Prefer fixed image tags or digests over mutable SNAPSHOT tags for reproducible deployments.
  • Review ABAC defaults and document deployment-specific rule sets outside the public chart when needed.

Catena-X Quick Start

The Catena-X example values deploy the parts needed for marker-based Digital Twin access:

  • digitalTwinRegistry stores shell descriptors and filters them through AAS descriptor markers.
  • submodelRepository stores submodels and filters them through submodel and submodel-element markers.
  • aasWebGui is enabled with the catena-x infrastructure template, which uses digitalTwinRegistry and submodelService endpoints instead of the separate Full component set.
  • keycloak is enabled for a self-contained quick start and initializes demo users and token mappers.
  • abac is enabled globally, with service-local rules for Digital Twin Registry and Submodel Repository.

Start from the example values:

cp values/values.catena-x.example.yaml values/values.my-catena-x-environment.yaml

Edit at least these values before installing:

host: basyx.example.com

tls:
  hosts:
    - basyx.example.com

keycloak:
  secrets:
    admin:
      password: change-me
    client:
      clientPassword: change-me

Also replace the demo user passwords before using the file outside a throwaway test namespace.

Then render and install:

helm lint charts/basyx -f values/values.my-catena-x-environment.yaml

helm upgrade --install basyx charts/basyx \
  -n basyx-catena-x \
  --create-namespace \
  -f values/values.my-catena-x-environment.yaml

The example initializes these demo users. All example passwords are change-me. The Postman collection uses OAuth2 password-grant requests for demo data loading. Depending on your Keycloak policies, demo users may need to log in through the Web UI once and complete required account setup before those token requests succeed. Change the passwords and disable direct access grants before using this setup beyond a disposable demo namespace.

User Initial password Purpose
catena-x.provider change-me Data provider with view_digital_twin, add_digital_twin, update_digital_twin and delete_digital_twin role claims.
catena-x.partner-a change-me Consumer with Edc-Bpn=BPN_COMPANY_001 for marker-based read access tests.
catena-x.partner-b change-me Consumer with Edc-Bpn=BPN_COMPANY_002 for marker-based read access tests.

CatenaXplorer EDC UI Settings

The Catena-X example also preconfigures the AAS Web UI with the CatenaXplorer EDC backend-for-frontend environment variables from the upstream CatenaXplorerEdcStandalone example.

Non-sensitive EDC settings are stored in aasWebGui.environment and rendered into the Web UI ConfigMap:

aasWebGui:
  environment:
    CX_EDC_BFF_ENABLED: "true"
    CX_EDC_BFF_PORT: "3001"
    CX_EDC_BFF_UPSTREAM_URL: "http://127.0.0.1:3001"
    CX_EDC_BFF_AUTH_MODE: none
    CX_EDC_DEFAULT_MANAGEMENT_URL: "https://consumer-edc.example.com/management"
    CX_EDC_DEFAULT_API_KEY_HEADER: X-Api-Key
    CX_EDC_DEFAULT_DSP_ENDPOINT: "https://consumer-edc.example.com/api/v1/dsp"
    CX_EDC_ALLOWED_COUNTER_PARTY_ADDRESSES: "https://provider-edc.example.com/api/v1/dsp"

The EDC Management API key is sensitive. Do not store a real API key in aasWebGui.environment. Use aasWebGui.secretEnvironment for disposable test overlays, or preferably reference an existing Secret in production:

aasWebGui:
  existingSecretEnvironment: basyx-aas-web-ui-edc

Create the Secret before installing:

kubectl -n basyx-catena-x create secret generic basyx-aas-web-ui-edc \
  --from-literal=CX_EDC_DEFAULT_API_KEY='change-me'

If aasWebGui.existingSecretEnvironment is set, the chart does not render aasWebGui.secretEnvironment; it only references the existing Secret from the Web UI Deployment.

The upstream BaSyx Go marker example also contains demo data:

Helm does not load those objects automatically. Download the files from the upstream example and load them explicitly after deployment.

This repository also includes a generic Postman collection for the Catena-X example:

examples/postman/basyx-catena-x-marker-access.postman_collection.json

The collection contains the marker example payloads and uses the default ingress paths from values/values.catena-x.example.yaml.

In the Web UI infrastructure configuration, submodelService.baseUrl points to the Submodel Repository service root, for example https://basyx.example.com/submodel-repo. The Web UI appends concrete API paths such as /submodels/{submodelId} itself.

Before running it, import the collection into Postman and adjust the collection variables:

Variable Default Description
baseUrl https://basyx.example.com Public ingress base URL without a trailing slash.
realm basyx Keycloak realm created by the example values.
clientId basyx-ui OAuth2 client used for password-grant token requests.
providerUsername catena-x.provider Demo provider user.
providerPassword change-me Demo provider password.
partnerAUsername catena-x.partner-a Demo consumer with Edc-Bpn=BPN_COMPANY_001.
partnerAPassword change-me Demo partner A password.
partnerBUsername catena-x.partner-b Demo consumer with Edc-Bpn=BPN_COMPANY_002.
partnerBPassword change-me Demo partner B password.

Run the folders in this order:

  1. Auth fetches OAuth2 access tokens for the provider and both partners.
  2. Setup - Write Example Data removes old test objects if present, then creates the shell descriptor and both submodels.
  3. Read - Provider, Read - Partner A, Read - Partner B and Read - Anonymous exercise the marker-based read paths with different token claims.

If Postman returns invalid_grant with Account is not fully set up, the Keycloak user still has a required action such as UPDATE_PASSWORD, VERIFY_EMAIL or user profile completion. Log in with that user through the Web UI or Keycloak account console first, complete the required setup, and run the Auth folder again.

Do not upload these files through the generic Web UI JSON/UML import. That import expects AAS or AAS Environment payloads and will reject shell-descriptor.json with no AAS imported. The marker example files must be written to their component APIs instead: shell-descriptor.json to Digital Twin Registry and the submodel JSON files to Submodel Repository. If you use the example data outside localhost, adjust embedded endpoint URLs in the descriptor to match your ingress host and paths.

The marker rules follow the BaSyx Go marker access example:

  • PUBLIC_READABLE marks descriptors or submodels that can be read publicly.
  • Partner-specific visibility is based on the Edc-Bpn token claim.
  • Digital Twin Registry checks specificAssetIds[].externalSubjectId.keys[].value and submodelDescriptors[].supplementalSemanticIds[].keys[].value.
  • Submodel Repository checks supplementalSemanticIds[].keys[].value on submodels and submodel elements.

For production Catena-X environments, prefer an external IAM or connector flow that issues a trustworthy Edc-Bpn token claim. Do not allow arbitrary external clients to set this claim through request headers. Header-to-claim injection should only be enabled behind a trusted ingress or connector mapping.

References

This README follows the deployment-oriented structure of the Eclipse BaSyx Helm chart documentation while adapting it for the BaSyx Go chart and its current dependencies.

About

This repository contains Helm charts for Eclipse BaSyx

Topics

Resources

Security policy

Stars

3 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages