Configuration
Repository Structure
civitas-core-deployment/
├── defaults/ # Default configuration (do not modify)
│ ├── environment/ # Global defaults, charts, images, secrets
│ │ ├── global.yaml # Master configuration file
│ │ └── *.yaml.gotmpl # Auto-aggregated component configs
│ └── deployment/ # Default deployment scaffolding
├── components/ # Component definitions
│ ├── prepare/ # Cluster preparation hooks
│ ├── secrets/ # Secret generation
│ ├── networkpolicies/ # Default-deny network policies
│ ├── postgres/ # PostgreSQL (CloudNativePG)
│ ├── etcd/ # etcd key-value store
│ ├── kafka/ # Apache Kafka (Strimzi)
│ ├── keycloak/ # Identity & access management
│ ├── authz/ # Authorization (Open Policy Agent)
│ ├── apisix/ # API gateway
│ ├── frost/ # FROST-Server (OGC SensorThings API)
│ ├── nifi/ # Apache NiFi data pipelines
│ ├── config-adapters/ # Configuration management
│ ├── portal/ # Web UI and backend API
│ ├── geoserver/ # Geospatial data server
│ ├── valkey/ # Valkey (Redis-compatible) cache
│ └── superset/ # Apache Superset BI dashboards
├── deployment/ # Your instance-specific configuration
│ ├── helmfile.yaml # Main entry point for Helmfile
│ └── environments/ # Environment-specific overrides
│ └── local/
├── dev-deployment/ # Local cluster setup scripts
├── helmfile-root.yaml.gotmpl # Root Helmfile template
└── helmfile-components.yaml.gotmpl # Component iteration template
Configuration Hierarchy
Configuration values are resolved in the following order (lowest to highest precedence):
defaults/environment/global.yaml- Global defaults (domain, profile, components list)defaults/environment/*.yaml.gotmpl- Aggregated component configs (charts, images, databases, secrets)components/<name>/default-environment.yaml.gotmpl- Per-component defaults (namespaces, feature flags)components/<name>/values/<part>/base-values.yaml.gotmpl- Base Helm valuescomponents/<name>/values/<part>/<profile>-values.yaml.gotmpl- Profile-specific values (development or production)deployment/environments/<env>/global.yaml.gotmpl- Your environment-specific overrides (highest precedence)
You should only modify files in the deployment/ directory. The defaults/ and components/ directories contain the upstream configuration and should not be changed for a specific deployment instance.
Setting up the Deployment Directory
The deployment/ directory is your instance-specific configuration.
It is not tracked by the civitas-core-deployment repository, so you can track your deployment configurations separately.
Create your deployment directory by copying the default deployment scaffolding:
cp -r defaults/deployment deployment
The deployment directory is in .gitignore of the CIVITAS/CORE v2 deployment repository.
Therefore, it can be used as git repository without affecting the main repository.
This is very handy for operators to track their own deployments with git.
A typical directory structure looks like this:
civitas-core-deployment/
├── ...
└── deployment
├── environments
│ ├── local
│ │ └── global.yaml.gotmpl # Local environment overrides
│ └── production
│ └── global.yaml.gotmpl # Production environment overrides
└── helmfile.yaml # Main Helmfile entry point (do not modify)
Creating Environments
An environment is a complete installation of the whole platform.
Environments are used for two main purposes:
- Deploy different stages of the platform like
testing,staging, orproduction - Deploy different installations for different clients like
client-a,client-b
It is possible to combine both purposes and have deployments for multiple clients and for each client multiple stages.
For this organize environments in subfolders like city-a/production and city-a/testing. The same names (with /) must be used in the helmfile.yaml file.
To create a new environment:
- Create a folder inside
deployment/environments/with the name of the environment - Add an empty
global.yaml.gotmplfile inside the environment folder - Add it to the
helmfile.yamlin theenvironmentssection with empty values - Add it to the
helmfilessection in thevalueslist underenvironments
---
environments:
# add here
city-a:
values: [ ]
---
helmfiles:
- path: "../helmfile-root.yaml.gotmpl"
values:
- environments:
- city-a # add here
Configuring the Environment
Most configuration options have sensible defaults and only need to be adjusted in special cases.
Therefore, in most cases, it is enough to adjust only a few global settings.
Everything you don't overwrite in the environment config files will be taken from the default values defined in defaults/environment/.
Start with the defaults/environment/global.yaml file and copy any values you need to overwrite into the global.yaml.gotmpl file of your environment.
Especially the global.instanceSlug must be set to the namespace name, that was created for this deployment.
By default all components run in a single namespace (global.singleNamespace: true). You can also give each component its own namespace by setting global.singleNamespace: false — see Deployment Concept → Namespace layout per instance to choose a model. The shipped components already handle both models, so switching is just this one flag. The per-component wiring for multi-namespace mode (namespace overrides, cross-namespace references) is only needed in special cases — when you author or customize components — and is a component-authoring concern, see Component Configuration → Referencing Components Across Namespaces.
The profile setting controls environment-specific behavior:
development (default):
- Lower resource requests and limits
- Debug-level logging
- Self-signed TLS certificates
- Single replicas
- Relaxed security policies
production:
- Higher resource requests and limits
- Info/warn-level logging
- Production TLS certificates (e.g. Let's Encrypt)
- Multiple replicas for high availability
- Strict security policies and network policies
- Prometheus metrics enabled
If you want to use linkerd without the patchNamespaces feature, you can manually patch the namespaces with the following command:
kubectl annotate namespace <namespace> linkerd.io/inject=enabled --overwrite
Exclude Components
If you don't want to deploy all components, you can copy the components list from defaults/environment/global.yaml into your environment global.yaml.gotmpl file and remove the components you don't want to deploy.
Many components depend on each other, so make sure to check the dependencies before removing components.
Configuration Reference
The following tables list all configurable parameters. Parameters are set in your environment's global.yaml.gotmpl file unless noted otherwise.
Global Parameters
| Parameter | Description | Default | Valid Values |
|---|---|---|---|
global.domain | DNS domain for all services | civitas.test | Any valid domain |
global.instanceSlug | Unique identifier for this deployment (used as namespace) | dev | Lowercase alphanumeric, max 63 chars |
global.profile | Deployment profile controlling resources and replicas | development | development, production |
global.createNamespaces | Whether Helmfile should create namespaces automatically | true | true, false |
global.singleNamespace | One namespace for all components (true) vs. one namespace per component (false) | true | true, false |
global.operators.namespace | Namespace for the shared cluster operators. Empty resolves to civitas-operators when using the operator/instance entrypoints, or the instance namespace in the legacy all-in-one deployment. See Instance and operator settings. | '' | Any valid namespace name |
global.operators.watchAllNamespaces | Whether the operators watch all namespaces so one deployment serves every instance. Set automatically by the operator entrypoint — leave at the default. | false | true, false |
Instance and operator settings
The identity and layout of an instance are driven by two per-environment settings — set them in your environment's global.yaml.gotmpl:
global:
instanceSlug: customer-a # namespace, Keycloak realm and subdomain prefix
domain: customer-a.example.com # DNS domain for this instance's services
instanceSlug must be a valid DNS-1123 label (lowercase alphanumeric and -) and unique on the cluster.
Operator namespace. By default the shared operators are deployed into civitas-operators — you don't need to configure anything. To use a different namespace, set global.operators.namespace once in your environment file; both the operator and the instance entrypoints read the same value, so the two layers always agree:
global:
operators:
namespace: shared-operators # optional; defaults to "civitas-operators"
On a cluster with more than one instance, the operator namespace must differ from every instanceSlug — otherwise destroying the instance that shares its namespace would tear down the operators that all the other instances depend on. The default civitas-operators guarantees this automatically.
For a single instance, co-location is fine and even useful: setting global.operators.namespace to the instance's own instanceSlug (with singleNamespace: true) collapses the operators and that instance into one namespace, while still using the two-layer flow (deploy-operators then deploy-instance). The prepare step's exclusion logic handles this cleanly — the namespace is prepared once, and there are no release-name collisions between the layers.
# operators + one instance in a single namespace
global:
instanceSlug: dev
singleNamespace: true
operators:
namespace: dev # == instanceSlug
global.operators.watchAllNamespaces is set automatically by the operator entrypoint — never set it by hand.
See Deployment Concept → How many instances per cluster for the two-layer model and Deployment → Deploying multiple instances for the commands.
Service Mesh
| Parameter | Description | Default | Valid Values |
|---|---|---|---|
global.serviceMesh.enable | Enable service mesh integration | true | true, false |
global.serviceMesh.type | Service mesh type | linkerd | linkerd |
global.serviceMesh.patchNamespaces | Auto-inject sidecars into namespaces | true | true, false |
global.serviceMesh.defaultInboundPolicy | Linkerd config.linkerd.io/default-inbound-policy applied to every instance namespace. cluster-authenticated enforces mandatory mTLS from meshed in-cluster clients. See Security Hardening. | cluster-authenticated | cluster-authenticated, all-authenticated, all-unauthenticated, audit |
global.serviceMesh.allowUnauthenticatedIngress | Whether the ingress controller may reach the APISIX data plane without mTLS. false requires a meshed ingress controller (see Prerequisites → Linkerd); set true only for environments with an unmeshed ingress. | false | true, false |
Ingress
| Parameter | Description | Default | Valid Values |
|---|---|---|---|
global.ingress.clusterIssuer | cert-manager ClusterIssuer for TLS certificates | selfsigned-ca | Any installed ClusterIssuer (e.g. letsencrypt-prod) |
global.ingress.ingressClass | Ingress controller class | nginx | Any installed IngressClass (e.g. nginx, traefik) |
Storage
| Parameter | Description | Default | Valid Values |
|---|---|---|---|
global.storage.storageClass.rwo | StorageClass for ReadWriteOnce volumes (databases) | '' (cluster default) | Any available StorageClass |
global.storage.storageClass.rwx | StorageClass for ReadWriteMany volumes (currently unused) | '' (cluster default) | Any available StorageClass |
global.storage.storageClass.loc | StorageClass for local storage | '' (cluster default) | Any available StorageClass |
Metrics
| Parameter | Description | Default | Valid Values |
|---|---|---|---|
global.metrics.enabled | Enable Prometheus metrics scraping across components | false | true, false |
Runtime Policies
Deploys the runtime-policies component — in-cluster Kyverno ClusterPolicy objects that enforce runtime invariants (Linkerd sidecar presence, protection against NetworkPolicy label spoofing by the shared operators). Requires Kyverno to be running in the cluster (see Prerequisites). Details in Security Hardening.
| Parameter | Description | Default | Valid Values |
|---|---|---|---|
global.runtimePolicies.enabled | Deploy the runtime Kyverno ClusterPolicy objects | true | true, false |
global.runtimePolicies.failureAction | Audit reports violations only; Enforce blocks violating resources at admission | Audit | Audit, Enforce |
The Linkerd-related runtime policies activate automatically only when the service mesh is enabled (global.serviceMesh.type: linkerd). Because these are cluster-scoped objects, the component is deployed once per cluster with the shared operator layer (helmfile -f deployment/helmfile-operators.yaml sync), not per instance — see Deploying multiple instances.
Components List
| Parameter | Description | Default |
|---|---|---|
components | Ordered list of components to deploy (order matters for dependencies) | See below |
Component-Specific Parameters
Component-specific settings are configured in .yaml.gotmpl files (e.g. <component-name>.yaml.gotmpl) files within your environment folder.
Overriding Helm Values Directly
Any Helm value of a component can be overridden via rawValues in the environment file:
# Example: Override keycloak replica count
keycloak:
app:
rawValues:
replicaCount: 3
# Example: Override PostgreSQL max_connections
postgres:
cluster:
rawValues:
cluster:
postgresql:
parameters:
max_connections: "1000"
Use rawValues with care. These values bypass the configuration hierarchy and may break synchronization between components.
Helm Defaults
Global Helm behavior is configured in defaults/helm-defaults.yaml:
| Parameter | Description | Default |
|---|---|---|
helmDefaults.wait | Wait for resources to be ready | true |
helmDefaults.waitForJobs | Wait for jobs to complete | true |
helmDefaults.timeout | Timeout for each release in seconds | 300 |
Configuring other default values
Some default files in the defaults/environment/ directory don't have values set, but only Go template code.
images:
{{ include "civitas.configFiles" (dict "components" .Values.components "file" "images.yaml") }}
These are values that are collected from all components and merged into one file.
You can see the values in the respective components/<component>/<filename>.yaml files.
They can be adjusted in the environment by overwriting the values with the top-level key in any of the environment .yaml.gotmpl files.
e.g. deployment/environments/local/images.yaml.gotmpl:
images:
keycloak:
app:
tag: "15.0.2"
# ...
Using external components
Postgres Databases
External postgres databases can be used instead of the CloudNativePG operator.
To do this, you can remove the postgres component from the components list and add the connection details of your external database in the environment config file.
For this create a file inside the environment folder named databases.yaml.gotmpl and for every databases.yaml file inside the components/<component>/ folders copy everything, set embedded: false, set secret.generate: false and add the connection details of your external database.
The secrets must be created manually in the cluster before deploying the platform.
Example for the portal component:
# databases.yaml.gotmpl
portal:
name: 'portal'
embedded: false
user: 'portal'
secret:
name: 'db-portal'
key: 'password'
generate: false
host: 'external-db-host.example.com'
port: 5432
Be sure to create a authz-readonly-user user with read-only access to the portal database.
The following databases with extensions are expected to be provided by an external database:
| Database Name | Required Extensions |
|---|---|
| flowable | |
| frost | postgis, postgis_topology, fuzzystrmatch, postgis_tiger_geocoder |
| geoserver | |
| keycloak | |
| payload_data | postgis |
| portal | |
| superset |
Kafka
To be documented...