Skip to main content
Version: V2-Next

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

EnvironmentSupport LevelNotes
Kubernetes (x86_64)Fully supportedProduction and development
Kubernetes (ARM64)Not officially supportedPlanned for the future
Docker ComposeNot availableUse the local k3d deployment for development
Virtual MachinesNot availableKubernetes 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

tip

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.

warning

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 ClusterPolicy objects. Install Kyverno in your cluster (see Prerequisites → Kyverno), or disable them with global.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: true for 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:

  1. Install CRDs for CloudNativePG and Strimzi operators
  2. Generate secrets for all components
  3. Deploy PostgreSQL, Etcd, and Kafka infrastructure
  4. 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.

warning

Order matters: the shared operators (and their CRDs) must exist before any instance is deployed.

info

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.

info

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

tip

Some helpful flags:

  • -i or --interactive: Shows a diff of what will be changed and prompts for confirmation before applying changes (only for apply command).
  • --selector component=<component-name>: Deploys only the specified component. The component name matches the component field in its civitas-component.yaml (usually the lowercase directory name; a few, like configAdapters, are camelCase).

You can set your kubeconfig with export KUBECONFIG=<path>. It uses always the current context set with kubectl config set-context.

tip

An interactive and safe way to deploy the platform, where you can review everything is the following:

  1. Install CRDS for CloudNativePG and Strimzi operators manually
  2. 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.