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 :
- Gateway
- Reporting
- Tenant Management backend
- License Management backend
- Task Scheduler backend
- Notification hub (SignalR)
- Neos AI backend
- Support Center backend
- Cluster backend
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 :
- Gateway
- Reporting
- Tenant Management backend
- License Management backend
- Task Scheduler backend
- Neos AI backend
- Support Center backend
- Cluster backend
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.