Table of Contents

Prerequisites

Business clusters images

In order to run your Neos cluster in the Kubernetes environment, you will need to provide for each cluster a Docker image of the backend and frontend parts. If your cluster calls background server methods, you will also need to provider a Docker image of the task-runner.

These images should be based on the same Neos version as the application version of the Helm chart.

Important

Docker image tags used in the examples below are illustrative. Some examples use explicit pinned tags for reproducibility, while others keep a convenience tag for readability. Choosing, updating, and patching base images (for example mcr.microsoft.com/dotnet/aspnet or nginx) is the responsibility of the team operating the deployment. In production, prefer explicit and traceable tags. From the framework perspective, the technical requirement is compatibility with the expected .NET minor version (for example .NET 10.x), not a specific patch tag or Linux distribution tag.

Warning

Keep the base image updated over time. Security fixes are delivered through image updates, so stale image tags can expose known vulnerabilities.

Backend example

# syntax=docker/dockerfile:1.24

# Use this dockerfile to generate technicaldemos-backend image.
#
# Build arguments :
# - NEOS_VERSION : neos-fmk image version to use for cluster generation (default: latest)
#
# /!\ The build command has to be run from the cluster root directory context :
#   docker build -t technicaldemos-backend:{version} -f ./docker/backend.Dockerfile --build-arg NEOS_VERSION={version} . #from technicaldemos root directory

ARG NEOS_VERSION=latest
# To build the application, we use a neos-fmk image as base so we can directly run the neos commands
FROM "harbor.hexanet.fr:8443/neos/neos-fmk:${NEOS_VERSION}" AS builder

COPY ./modules /usr/src/technicaldemos/modules
COPY ./*.yml /usr/src/technicaldemos/

# The persistence configuration is mandatory for the build but it can be overridden in the appsettings.json file
RUN echo 'PersistenceType: PostgreSQL' > /usr/src/technicaldemos/technicaldemos.persistence.yml
# /!\ DONT PUT YOUR REAL CONNECTIONSTRING HERE /!\
# We just want to generate the application so we need to set a value but it will not be used by the process.
# In production the connectionstring should be overridden either by :
# - mounting a specific appsettings.json file
# - setting the PersistenceSettings__ConnectionString environment variable
RUN echo 'ConnectionString: None' >> /usr/src/technicaldemos/technicaldemos.persistence.yml
WORKDIR /usr/src/technicaldemos

# We only need to build the server part of the cluster
RUN neos generate --build-configuration Release Server

# The backend will run inside a container having dotnet installed
# Use the most recent available tag for the targeted .NET minor version (for example 10.0.x)
FROM mcr.microsoft.com/dotnet/aspnet:10.0.11-alpine3.23

# Install globalization functionality
RUN apk add --upgrade --no-cache \
    icu-data-full \
    icu-libs \
    tzdata

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false

# Copy the result of the AspNetCore application build into the dotnet image
COPY --from=builder /usr/src/technicaldemos/server/bin/TechnicalDemos.AspNetCore/ /app

# Create an unprivileged user to run the application in an isolated directory (/app)
RUN addgroup -g 1001 -S neos && adduser -u 1001 -S -G neos neos && chown -R neos /app
USER 1001

WORKDIR /app

# Indicates the port on with the backend will be listening
ENV ASPNETCORE_URLS=http://+:7000

# When the container is started, it will run the application backend server
ENTRYPOINT ["dotnet", "TechnicalDemos.AspNetCore.dll"]

Task-runner example

# syntax=docker/dockerfile:1.24

# Use this dockerfile to generate technicaldemos-task-runner image.
#
# Build arguments :
# - NEOS_VERSION : neos-fmk image version to use for cluster generation (default: latest)
#
# /!\ The build command has to be run from the cluster root directory context :
#   docker build -t technicaldemos-task-runner:{version} -f ./docker/task-runner.Dockerfile --build-arg NEOS_VERSION={version} . #from technicaldemos root directory

ARG NEOS_VERSION=latest
# To build the application, we use a neos-fmk image as base so we can directly run the neos commands
# Use the most recent available tag for the targeted .NET minor version (for example 10.0.x)
FROM "harbor.hexanet.fr:8443/neos/neos-fmk:${NEOS_VERSION}" AS builder

COPY ./modules /usr/src/technicaldemos/modules
COPY ./*.yml /usr/src/technicaldemos/

# The persistence configuration is mandatory for the build but it can be overridden in the appsettings.json file
RUN echo 'PersistenceType: PostgreSQL' > /usr/src/technicaldemos/technicaldemos.persistence.yml
# /!\ DONT PUT YOUR REAL CONNECTIONSTRING HERE /!\
# We just want to generate the application so we need to set a value but it will not be used by the process.
# In production the connectionstring should be overridden either by :
# - mounting a specific appsettings.json file
# - setting the PersistenceSettings__ConnectionString environment variable
RUN echo 'ConnectionString: None' >> /usr/src/technicaldemos/technicaldemos.persistence.yml
WORKDIR /usr/src/technicaldemos

# We only need to build the server part of the cluster
RUN neos generate --build-configuration Release Server

# The task-runner will run inside a container having dotnet installed
FROM mcr.microsoft.com/dotnet/aspnet:10.0.11-alpine3.23

# Install globalization functionality
RUN apk add --upgrade --no-cache \
    icu-data-full \
    icu-libs \
    tzdata

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false

# Copy the result of the task-runner application build into the dotnet image
COPY --from=builder /usr/src/technicaldemos/server/bin/TechnicalDemos.TaskRunner/ /app

# Create an unprivileged user to run the application in an isolated directory (/app)
RUN addgroup -g 1001 -S neos && adduser -u 1001 -S -G neos neos && chown -R neos /app
USER 1001

WORKDIR /app

# Indicates the port on with the task-runner will be listening
ENV ASPNETCORE_URLS=http://+:7000

# When the container is started, it will run the application task-runner server
ENTRYPOINT ["dotnet", "TechnicalDemos.TaskRunner.dll"]

Frontend example

Important

Neos generates a static frontend (client/dist). It can be served by any static hosting solution (Nginx, Apache, Caddy, object storage + CDN, Kubernetes ingress static hosting, etc.). The Nginx-based Dockerfile below is only one possible implementation.

Warning

Keep the base image updated over time. Security fixes are delivered through image updates, so stale image tags can expose known vulnerabilities.

# syntax=docker/dockerfile:1.24

# Use this dockerfile to generate technicaldemos-frontend image.
#
# Build arguments :
# - NEOS_VERSION : neos-fmk image version to use for cluster generation (default: latest)
#
# Use this dockerfile to generate technicaldemos-frontend image.
# /!\ The build command has to be run from the cluster root directory context :
#   docker build -t technicaldemos-frontend:{version} -f ./docker/frontend.Dockerfile --build-arg NEOS_VERSION={version} . #from technicaldemos root directory

ARG NEOS_VERSION=latest
# To build the application, we use a neos-fmk image as base so we can directly run the neos commands
FROM "harbor.hexanet.fr:8443/neos/neos-fmk:${NEOS_VERSION}" AS builder

COPY ./modules /usr/src/technicaldemos/modules
COPY ./*.yml /usr/src/technicaldemos/

# The persistence configuration is mandatory for the build but it can be overridden in the appsettings.json file
RUN echo 'PersistenceType: PostgreSQL' > /usr/src/technicaldemos/technicaldemos.persistence.yml
# /!\ DONT PUT YOUR REAL CONNECTIONSTRING HERE /!\
# We just want to generate the application so we need to set a value but it will not be used by the process.
# In production the connectionstring should be overridden either by :
# - mounting a specific appsettings.json file
# - setting the PersistenceSettings__ConnectionString environment variable
RUN echo 'ConnectionString: None' >> /usr/src/technicaldemos/technicaldemos.persistence.yml

WORKDIR /usr/src/technicaldemos
# Build the cluster
RUN neos generate --build-configuration Release

WORKDIR /usr/src/technicaldemos/client
# Build the client website
RUN npm install
RUN npm run build

# Example only: using Nginx to serve the generated static files
# In production, prefer an explicit and traceable tag (for example: nginx:<major>.<minor>.<patch>-alpine<version>-slim)
FROM nginx:stable-alpine-slim
# Provides some basic nginx configuration
COPY ./docker/frontend/nginx/nginx.conf /etc/nginx/conf.d/default.conf
# Copy the client website files into the nginx serving directory
COPY --from=builder /usr/src/technicaldemos/client/dist /usr/share/nginx/html

With nginx.conf file :

server_tokens off;

server {
  listen 80;
  server_name frontend;
  underscores_in_headers on;

  root /usr/share/nginx/html;

  location / {
    rewrite ^/$ /index.html break;
    try_files $uri =404;
    add_header Cache-Control "no-cache";
  }

  location /index.html {
    add_header Cache-Control "no-cache";
  }
}

Base image security and maintenance

Keeping container base images up to date is part of deployment operations.

  • Regularly update base image tags to include security fixes.
  • Scan produced images for vulnerabilities in your CI/CD pipeline.
  • Rebuild and redeploy images when upstream security advisories are published.
  • Prefer explicit and traceable tags in production (avoid relying on long-lived floating tags).

Secrets

In a deployed environnement, all sensitive data should be stored in Kubernetes secrets. Neos backend services (cluster, Tenant Management, reports, ...) use Dapr secret stores to read values from Kubernetes secrets and mount them as dotnet configuration.

Note

To create a simple key value secret in Kubernetes which will be usable in Neos you can use the following command (example for authentication secret in Powershell) :

kubectl create secret generic neos-auth-env `
  --from-literal=Authentication__Preset="AzureAdB2C" `
  --from-literal=Authentication__Authority="https://SomeApplication.b2clogin.com/SomeApplication.onmicrosoft.com/B2C_1A_SIGNUP_SIGNIN/v2.0" `
  --from-literal=Authentication__ClientId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
  --from-literal=Authentication__ClientSecret="somesecretvalue" `
  --from-literal=Authentication__Scopes__0="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Authentication

Because authentication configuration needs to be known by gateway you must set the authenticationSecret property in your Helm values files so it contains all the necessary variables for authentication.

Note

The environment variables are dotnet override of the appsettings.json file. Please see this article for more information.

Example (Neos development authentication server) :

Authentication__Preset=NeosDevAuth

Example (Azure AD B2C) :

Authentication__Preset=AzureAdB2C
Authentication__Authority=https://SomeApplication.b2clogin.com/SomeApplication.onmicrosoft.com/B2C_1A_SIGNUP_SIGNIN/v2.0
Authentication__ClientId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Authentication__ClientSecret=somesecretvalue
Authentication__Scopes__0=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Note

If one or several cluster in your deployment is not subject to authentication you need to add their host to the AnonymousHosts configuration of the gateway :

AnonymousHosts__0=first.unauthenticated.cluster.com
AnonymousHosts__1=second.unauthenticated.cluster.com

Gateway

The gateway provides authentication and routing to the right cluster version. You can override dotnet configuration using environment variables. These environment variables should be set in a secret which will then be mounted inside the gateway container.

Note

For gateway routing and unavailable-tenants synchronization, see Gateway Routing, CORS, and Unavailable Tenants Settings. For CORS parameters, see Gateway CORS configuration.

You can set the secret name as the value of gateway.envSecret property in your Helm values file.

Note

Status code pages can be configured for the gateway, including dedicated pages for disabled or unavailable tenants. See Custom status code pages for the available settings, image layout, and Dockerfile example.

Note

If you configured a secret for authenticationSecret, its variables will be mounted inside the gateway container.

Note

If your application has server methods which are not subject to authentication (eg Authentication required set to false in the server method configuration), you need to add them to the AnonymousRoutes configuration of the gateway :

AnonymousRoutes__0=/webapi/firstanonymousmethod
AnonymousRoutes__1=/webapi/secondanonymousmethod

Reporting

The Stimulsoft license has to be configured inside the report container so you will need to provide the license key in a secret for the Reporting__StimulsoftLicenceKey environment variable.

You can set the secret name as the value of report.envSecret property in your Helm values file.

Tenant Management

The Tenant Management needs to run with a database so you'll need to provide the connection string in a secret for the PersistenceSettings__ConnectionString environment variable.

Note

Connection string needs to follow the DotNet syntax.

Some fields (like database connection strings) are encrypted in database, so it is necessary to configure an encryption key in the secret. The key must be set as the value of EncryptionKey in the secret.

Note

The value of the key must be a 32-byte key encoded in base64; you can generate it in PowerShell with the following command:

[Convert]::ToBase64String((1..32 | ForEach-Object {Get-Random -Minimum 0 -Maximum 256}))
Warning

For Oracle database, you'll also need to disable concurrency access mode. To do so, you should add PersistenceSettings__DefaultConcurrencyAccessMode key with its value set to Disabled.

You can set the secret name as the value of tenantManagement.envSecret property in your Helm values file.

If you need to expose the Tenant Management cluster outside of Kubernetes, you need to provide a TLS certificate as a Kubernetes secret.

You can set the secret name as the value of tenantManagement.tlsSecret property in your Helm values file.

License Management

The License Management needs to run with a database so you'll need to provide the connection string in a secret for the PersistenceSettings__ConnectionString environment variable.

Note

Connection string needs to follow the DotNet syntax.

You can set the secret name as the value of licenseManagement.envSecret property in your Helm values file.

If you need to expose the License Management cluster outside of Kubernetes, you need to provide a TLS certificate as a Kubernetes secret.

You can set the secret name as the value of licenseManagement.tlsSecret property in your Helm values file.

Task runner actor state store (PostgreSQL)

Neos ecosystem relies on Dapr workflow components for its background server methods to run.

In the Kubernetes cluster, a specific actor state store component needs to be configured. See this article for supported state stores.

Neos Helm chart generates the component specs for Redis and PostgreSQL (v1).

By default the chart will generate the Redis actor state store component spec.

Warning

Redis is not recommended by Dapr as actor state store in production due to its limitations regarding transactions. Thus, we recommend to use postgresql preset instead.

In case you use the PostgreSQL (v1) component spec (actorStateStore.preset property of the Helm values file set to postgresql) you'll need to provide the name of a secret with the PostgreSQL connection string to use in the actorStateStore.postgresqlConnectionStringSecret property of your Helm values file.

The secret should have a connectionString entry like the following example :

kubectl create secret generic neos-actor-state-store-postgresql-secret `
  --from-literal=connectionString="host=localhost user=postgres password=example port=5432 connect_timeout=10 database=my_db"

Neos AI

The Neos AI cluster needs to run with a database so you'll need to provide the connection string in a secret for the PersistenceSettings__ConnectionString environment variable.

Note

Connection string needs to follow the DotNet syntax.

You can set the secret name as the value of neosAI.envSecret property in your Helm values file.

If you need to expose the Neos AI cluster outside the Kubernetes cluster, you need to provide a TLS certificate as a Kubernetes secret.

You can set the secret name as the value of neosAI.tlsSecret property in your Helm values file.

Task Scheduler

The Task Scheduler cluster needs to run with a database so you'll need to provide the connection string in a secret for the PersistenceSettings__ConnectionString environment variable.

Note

Connection string needs to follow the DotNet syntax.

You can set the secret name as the value of taskScheduler.envSecret property in your Helm values file.

If you need to expose the Task Scheduler cluster outside the Kubernetes cluster, you need to provide a TLS certificate as a Kubernetes secret.

You can set the secret name as the value of taskScheduler.tlsSecret property in your Helm values file.

Support Center

The Support Center cluster needs to run with a database so you'll need to provide the connection string in a secret for the PersistenceSettings__ConnectionString environment variable.

Note

Connection string needs to follow the DotNet syntax.

You can set the secret name as the value of supportCenter.envSecret property in your Helm values file.

If you need to expose the Support Center cluster outside the Kubernetes cluster, you need to provide a TLS certificate as a Kubernetes secret.

You can set the secret name as the value of supportCenter.tlsSecret property in your Helm values file.

Clusters

If you need to expose the cluster application outside the Kubernetes cluster, you need to provide a TLS certificate as a Kubernetes secret.

You can set the secret name as the value of clusters[n].tlsSecret property in your Helm values file.

If the cluster's backend needs to run with a database so you'll need to provide the connection string in a secret for the PersistenceSettings__ConnectionString environment variable.

Note

Connection string needs to follow the DotNet syntax.

You can set the secret name as the value of clusters[n].backend.envSecret property in your Helm values file.

To allow the Kubernetes cluster to pull the images of your cluster, you may need to create a secret containing your registry credentials.

You can set the secret names as the values of clusters[n].backend.imagePullSecret and clusters[n].frontend.imagePullSecret property in your Helm values file.

Configuration with AzureAD B2C

If you use AzureAD B2C authentication, you'll need to provide configuration to Tenant Management and your clusters using UserPermissions. This configuration will allow UserPermissions to create or read AzureAD B2C users.

You can set in secrets to setup the environment variables in the following example :

AuthenticationMode=AzureB2C
AzureB2COption__ClientId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AzureB2COption__TenantId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AzureB2COption__TenantName=xxxx.onmicrosoft.com
AzureB2COption__ClientSecretKey=yoursecret
AzureB2COption__B2cExtensionAppClientId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AzureB2COption__EmailContent__RedirectUrl=https://yourapp/
AzureB2COption__EmailContent__Title=Your title
AzureB2COption__EmailContent__Content1=Your email content
AzureB2COption__EmailContent__Content2=Your email content2
AzureB2COption__EmailContent__LinkText=Text
SendGrid__Key=yoursecret
SendGrid__From__Name=yourappname
[email protected]

Configure the first administrator user with UserPermissions module

If your deployed application is not multi-tenant, when the database is created, there are no users yet. To create the first administrator user, you can configure a new environment variable UserPermissions__DefaultUserAccountLogin or see Configuration in ASP.NET Core.

When the database is migrated, this user will be automatically created with all permissions.

Sending logs to Application Insights

Each Neos application can be configured to send logs to Application Insights, please see this article for more information.

Warning

Backend .NET SDK-based Application Insights settings in this chapter are deprecated and will be removed in a future version. Prefer OpenTelemetry Collector routing for backend telemetry and exporters. See OpenTelemetry collector migration.

Environnement variables for .Net processes

You can configure Application Insights for Neos .Net processes by setting options as environnement variables in the secret associated to the process.

The following processes are supported :

Example (cluster backend)

ApplicationInsights__ConnectionString=1a11a111-1a1a-1111-a111-1aaa1a1a1a11;IngestionEndpoint=https://westeurope-5.in.  applicationinsights.azure.com/;LiveEndpoint=https://westeurope.livediagnostics.monitor.azure.com/;  ApplicationId=2b22b222-2b2b-2222-b222-2bbb2b2b2b22
ApplicationInsights__EnableHeartbeat=false
ApplicationInsights__RoleName=NorthwindServer
ApplicationInsights__RequestCollectionOptions__TrackExceptions=true
Note

For more information on available options, see this article.

Log level

The default setting for Application Insights is to only capture Warning and more severe logs (see this article).

It is possible to modify this configuration with the Logging_ApplicationInsights__LogLevel__Default environment variable.

In the following example, Information and more severe logs will be sent to Application Insights even if Debug logs are emitted in the console output :

Logging__LogLevel__Default=Debug
Logging__ApplicationInsights__LogLevel__Default=Information
Exclude specific successful endpoints

By default, all request and traces will be sent to Application Insights except healthcheck (/hc & /healthz) and metrics (/metrics) endpoints if their status code is unset or less than 400.

If you want to exclude traces and requests associated to specific endpoints when the status code is unset or less than 400, you can set the ApplicationInsights__ExcludedSuccessfulEndpoints value (string array).

Warning

Don't forget to include /hc, /healthz and /metrics in the array in addition to your specific endpoints otherwise it could be overridden.

Example to exclude /api/v1/methods/some-specific-method and /api/v1/SomeEntityView request and traces when the response status code is unset or less than 400 :

ApplicationInsights__ExcludedSuccessfulEndpoints__0=/hc
ApplicationInsights__ExcludedSuccessfulEndpoints__1=/healthz
ApplicationInsights__ExcludedSuccessfulEndpoints__2=/metrics
ApplicationInsights__ExcludedSuccessfulEndpoints__3=/api/v1/methods/some-specific-method
ApplicationInsights__ExcludedSuccessfulEndpoints__4=/api/v1/SomeEntityView

Open Telemetry

You can configure Open telemetry for Neos .Net processes by setting options as environnement variables in the secret associated to the process.

The following processes are supported :

Tracing

Exclude specific endpoints

By default, /hc, /healthz and /metrics endpoints are not sent by open telemetry exporter.

You can customize the list of excluded endpoints by setting the Tracing__ExcludedEndpoints value (string array).

Warning

Don't forget to include /hc, /healthz and /metrics in the array in addition to your specific endpoints otherwise it could be overridden.

Example to exclude /api/v1/methods/some-specific-method and /api/v1/SomeEntityView traces :

Tracing__ExcludedEndpoints__0=/hc
Tracing__ExcludedEndpoints__1=/healthz
Tracing__ExcludedEndpoints__2=/metrics
Tracing__ExcludedEndpoints__3=/api/v1/methods/some-specific-method
Tracing__ExcludedEndpoints__4=/api/v1/SomeEntityView

JSON configuration for frontend

To configure Application Insights for frontend, you can create a secret containing an applicationInsights.json file content.

Example

JSON file content :

{
  "clusterVersion": "0.5.1",
  "connectionString": "1a11a111-1a1a-1111-a111-1aaa1a1a1a11;IngestionEndpoint=https://westeurope-5.in.  applicationinsights.azure.com/;LiveEndpoint=https://westeurope.livediagnostics.monitor.azure.com/;  ApplicationId=2b22b222-2b2b-2222-b222-2bbb2b2b2b22",
  "correlationHeaderExcludedDomains": ["domain1.com", "domain2.com"],
  "disableAjaxTracking": true,
  "enableAutoRouteTracking": true,
  "enableCorsCorrelation": true,
  "enableUnhandledPromiseRejectionTracking": true,
  "neosVersion": "1.21.0",
  "roleName": "NorthwindClient"
}

Associated secret :

apiVersion: v1
kind: Secret
data:
  applicationInsights.json: <base64encodedJSONfilecontent>
metadata:
  name: northwind-frontend-conf
Note

To create the secret directly from an existing applicationInsights.json file on your computer you can use the following command :

kubectl create secret generic <your-secret-name> --from-file=path/to/the/file/applicationInsights.json

See this article for more information on how to create secrets in Kubernetes.

Note

For more information on available options, see this article.

Ingress controller

This charts is configured to work with a Nginx ingress controller.