Deployment
Deployment Model
CIVITAS/CORE V2 is deployed exclusively on Kubernetes using Helmfile to orchestrate multiple Helm charts. There is no Docker Compose or VM-based deployment model.
Supported Environments
| Environment | Support Level | Notes |
|---|---|---|
| Kubernetes (x86_64) | Fully supported | Production and development |
| Kubernetes (ARM64) | Not officially supported | Planned for the future |
| Docker Compose | Not available | Use the local k3d deployment for development |
| Virtual Machines | Not available | Kubernetes is required |
Which entrypoint you use depends on your deployment topology:
- Single instance per cluster — use the all-in-one entrypoint (
deployment/helmfile.yaml), which deploys the platform together with its cluster operators. Covered in First Deployment below. - Multiple instances per cluster — deploy the shared operators once, then each instance separately. Covered in Deploying multiple instances.
First Deployment
The commands below use the all-in-one entrypoint (deployment/helmfile.yaml), which deploys the platform together with its cluster operators — ideal for a single instance per cluster. For multiple instances, see Deploying multiple instances.
Before deploying, make sure the prerequisites are in place. Beyond a cluster with an ingress controller, cert-manager and a storage class, the default configuration also needs:
- Kyverno running in the cluster — the all-in-one deploy installs the runtime
ClusterPolicyobjects. Install Kyverno in your cluster (see Prerequisites → Kyverno), or disable them withglobal.runtimePolicies.enabled: false. - Linkerd with a meshed ingress if the service mesh is enabled (the default) — mandatory mTLS otherwise rejects ingress traffic. Install linkerd and mesh your ingress as well or set
global.serviceMesh.allowUnauthenticatedIngress: truefor an unmeshed ingress.
To deploy a certain environment (e.g., testing), navigate to your deployment directory and run:
helmfile -f deployment/helmfile.yaml sync -e testing
This will:
- Install CRDs for CloudNativePG and Strimzi operators
- Generate secrets for all components
- Deploy PostgreSQL, Etcd, and Kafka infrastructure
- Deploy application components (Keycloak, Portal, APISIX, etc.)
The component deployment order is defined by the components list in global.yaml and respects inter-component dependencies.
Deploying multiple instances
To host several independent instances on one cluster, the deployment is split into a shared operator layer (deployed once per cluster) and a per-instance instance layer. See Deployment Concept → How many instances per cluster for the model and Configuration → Instance and operator settings for instanceSlug and the operator namespace.
Order matters: the shared operators (and their CRDs) must exist before any instance is deployed.
Kyverno must already be running in the cluster before this step, because the operator layer creates the cluster-scoped runtime ClusterPolicy objects. Install it on your cluster before the platform deployment — see Prerequisites → Kyverno.
1. Deploy the shared operators (once per cluster)
helmfile -f deployment/helmfile-operators.yaml sync -e <env>
This installs the CloudNativePG and Strimzi operators (and their CRDs) into the operator namespace (civitas-operators by default), configured to watch all namespaces, plus the cluster-scoped runtime Kyverno policies (runtime-policies). Re-running is idempotent.
2. Deploy each instance
Either select an instance via its environment, or override the slug ad-hoc:
# via environment (recommended for real instances — also carries domain etc.)
helmfile -f deployment/helmfile-instance.yaml.gotmpl sync -e <env>
# ad-hoc slug override (namespace + Keycloak realm = <slug>)
helmfile -f deployment/helmfile-instance.yaml.gotmpl sync -e <env> --state-values-set-string instanceSlug=<slug>
Repeat step 2 for every instance. Each instance is deployed into its own namespace and never touches the operators or the other instances.
Remember to create each instance's required secrets (e.g. keycloak-smtp) in that instance's namespace before deploying it — see Prerequisites → Secrets.
Rendering without applying (dry run)
helmfile -f deployment/helmfile-operators.yaml template -e <env>
helmfile -f deployment/helmfile-instance.yaml.gotmpl template -e <env> [--state-values-set-string instanceSlug=<slug>]
Operations
For multi-instance clusters, keep the two layers in mind when operating the platform:
- Operator upgrades affect all instances simultaneously — change the chart version and run
helmfile -f deployment/helmfile-operators.yaml sync -e <env>once. Apply operator/CRD upgrades before instances start using new custom-resource fields. - Removing an instance (
helmfile -f deployment/helmfile-instance.yaml.gotmpl destroy -e <env> [--state-values-set-string instanceSlug=<slug>]) leaves the shared operators and all other instances untouched. - Removing the operators would break reconciliation for all instances — only do this when tearing down the whole platform.
See also Updating for the general upgrade flow.
Subsequent Deployments
If CRDs are already installed, it is also possible to run apply, which will only apply changes instead of doing a full sync. This is faster for subsequent deployments after the initial deployment.
helmfile -f deployment/helmfile.yaml apply -e testing
Deploying a Single Component
To deploy or update a specific component without redeploying everything:
helmfile -f deployment/helmfile.yaml apply -e testing --selector component=keycloak
Replace keycloak with the component name. The name is the component field from the component's civitas-component.yaml — usually the lowercase directory name (e.g. keycloak, apisix, postgres), though a few use camelCase (e.g. the config-adapters directory is the configAdapters component). This is useful for iterating on individual components.
Dry-Run / Preview
To see what would be deployed without making changes:
# Render all templates
helmfile -f deployment/helmfile.yaml template -e testing
# Show changes that would be applied
helmfile -f deployment/helmfile.yaml diff -e testing
# Validate templates without deploying
helmfile -f deployment/helmfile.yaml lint -e testing
# Preview the namespaces that will be created (useful in multi-namespace mode)
helmfile -f deployment/helmfile.yaml template -e testing | grep -E '^\s*namespace:'
Tips and Tricks
Some helpful flags:
-ior--interactive: Shows a diff of what will be changed and prompts for confirmation before applying changes (only forapplycommand).--selector component=<component-name>: Deploys only the specified component. The component name matches thecomponentfield in itscivitas-component.yaml(usually the lowercase directory name; a few, likeconfigAdapters, are camelCase).
You can set your kubeconfig with export KUBECONFIG=<path>. It uses always the current context set with kubectl config set-context.
An interactive and safe way to deploy the platform, where you can review everything is the following:
- Install CRDS for CloudNativePG and Strimzi operators manually
- Run
helmfile -e testing apply -i --selector component=<component-name>for each component
Runnable Example
A minimal production deployment from scratch:
# 1. Clone the deployment repository
git clone https://gitlab.com/civitas-connect/civitas-core/civitas-core-v2/civitas-core-deployment.git
cd civitas-core-deployment
# 2. Create your deployment directory
cp -r defaults/deployment deployment
# 3. Create a production environment
mkdir -p deployment/environments/production
# 4. Configure the environment
cat > deployment/environments/production/global.yaml.gotmpl << 'EOF'
global:
domain: civitas.example.com
instanceSlug: prod
profile: production
initialUserEmail: replace-with-your-email@example.com
ingress:
clusterIssuer: 'letsencrypt-prod'
ingressClass: 'nginx'
EOF
# 5. Register the environment in helmfile.yaml
# (edit deployment/helmfile.yaml to add 'production' to environments and helmfiles sections)
# 6. Create required secrets
kubectl create namespace prod
kubectl create secret generic keycloak-smtp \
--from-literal=host='smtp.example.com' \
--from-literal=port='587' \
--from-literal=from='noreply@example.com' \
--from-literal=user='noreply@example.com' \
--from-literal=password='YOUR_SMTP_PASSWORD' \
-n prod
# 7. Deploy
helmfile -f deployment/helmfile.yaml sync -e production
For initialUserEmail, a platform admin will be created later. This is your initial user to log into the portal and to create other desired users.