Installation

k8s Deployment Manual

Anjana Deployment

Kit architecture

The anjana-k8s kit is a root Helm chart (anjana-platform) with three subcharts:

  • anjana-core: 12 microservices + 2 frontends (horus, portuno, zeus, kerno, minerva, viator, hermes, tot, drittesta, anjana-ui, portuno-ui, marketplace, adp-mcp)

  • anjana-persistences: in-cluster persistences (PostgreSQL, MongoDB, RabbitMQ, Valkey, Solr, SeaweedFS), each independently enabled/disabled. Disabled by default in cloud (managed services are used instead: RDS, DocumentDB, ElastiCache, Amazon MQ).

  • anjana-plugins: 20 connectors, all disabled by default

Horus acts as a centralized Config Server: all microservices query it at startup to obtain their configuration (connection strings to MongoDB, RabbitMQ, Solr, S3, license, etc.).

Requirements

Required connectivity

The kit will download all necessary resources from the Anjana Data central repository over HTTPS protocol and port 443 against the server dr-releases.anjanadata.com, so connectivity from the servers/cluster to be provisioned to that service will be required.

Access and credentials must be requested from the Anjana Data team.

Prerequisites on the deployment machine

  • kubectl: connected to the target cluster (kubeconfig configured)

  • helm >= 3.12: Kubernetes chart manager

  • anjana CLI: credential management tool (anjana in PATH)

  • cert-manager (>= 1.14): required only for TLS internal mode. Not included in the kit: the client must have it previously installed in the cluster. If cert-manager is not available, use TLS manual mode.

Cluster resource requirements (sizing)

The kit sizes the microservices according to the active environment profile (global.environment.profile in the values file): DEV (lab/local), PRE (pre-production/UAT, default value), or PRO (production). The profile sets the CPU and memory requests and limits for each service; the per-service detail can be checked in charts/anjana-core/values.yaml and adjusted individually if needed (see k8s Advanced, "Environment profiles").

Minimum guideline capacity that the cluster must reserve for the Anjana microservices (includes the 2 horus replicas):

Profile

CPU (requests)

RAM (requests)

RAM (limits)

DEV

~2 vCPU

~8 Gi

~20 Gi

PRE

~2.5 vCPU

~12.5 Gi

~22 Gi

PRO

~4 vCPU

~16.5 Gi

~25.5 Gi

If the in-cluster persistences are enabled (anjana-persistences.enabled: true), additionally add:

Persistence

RAM (requests / limits)

Default storage

PostgreSQL

512Mi / 1Gi

10Gi

MongoDB

512Mi / 1Gi

10Gi

RabbitMQ

512Mi / 1Gi

5Gi

Valkey

256Mi / 512Mi

5Gi

Solr

4Gi / 5Gi

15Gi (10Gi data + 5Gi configuration)

SeaweedFS

512Mi / 1Gi

20Gi

With all persistences enabled: approximately 6.5 Gi of additional RAM in requests and 65 Gi of storage.

NOTE: these figures cover only the Anjana platform. The cluster must also have capacity available for Kubernetes' own components and any other deployed workload.

Required adjustments

IMPORTANT: Since 26.k1 a TLS certificate is required for secure communication between microservices and access to the frontends. In internal mode (default), cert-manager generates and renews the certificate automatically. In manual mode, it is necessary to export the certificate paths before running anjana setup (see advanced guide).

The kit comes preconfigured with recommended settings. You will need to prepare the credentials bundle with the connection strings and license for your environment by following the three steps below.

Installation guardrails

The chart validates at render time that the prerequisites for the chosen configuration exist (for example the ClusterIssuer and the Secrets for TLS internal mode, or that exactly one Config Server backend is enabled). The behavior is controlled with global.guardrails.mode in the values file:

  • strict (default value): helm install / helm upgrade fails with a descriptive message if any prerequisite is missing. ✅RECOMMENDED in production.

  • permissive: only emits warnings and continues. Useful in a lab or when validating values locally with make test.

IMPORTANT: if helm install fails with a guardrails error, the message indicates exactly which prerequisite is missing. Fix it before retrying; do not switch to permissive to bypass it in production environments.

Infrastructure Secrets

The kit requires three Kubernetes Secrets created before the helm install:

Secret

Content

Created by

anjanadr

Docker Registry credentials

anjana setup

anjana-keystore

TLS: PKCS12 keystore, cacerts, PEM certs, passwords

anjana setup (manual or cert-manager)

anjana-secrets

All platform credentials: DB, MongoDB, RabbitMQ, Valkey, S3, license, domain

anjana setup (decrypts secrets/anjana.env.aes)

Anjana Installation - 3 steps

Step 1 - Prepare the credentials bundle

The kit uses an encrypted credentials bundle (secrets/anjana.env.aes) as the single source of truth for all platform secrets: connection strings, licenses, persistence credentials, and service configuration.

Copy the template included in the kit, fill in all the values, and generate the encrypted bundle:

Bash
cp anjana.env.example horus.env
vi horus.env                                        # fill in with the environment's values

anjana horus update --from-env horus.env            # generates secrets/anjana.env.aes

Or alternatively, from a JSON payload (CI/automation):

Bash
anjana horus update --from-json payload.json

NOTE: The secrets/anjana.env.aes file is encrypted with AES and can be safely versioned in Git. The plain text (horus.env) must never be uploaded to the repository.

Step 2 - Create namespace and Kubernetes Secrets

The anjana setup command creates the namespace, the pull secrets, and decrypts the AES bundle to create the necessary Secrets in the cluster:

Bash
anjana setup [namespace]   # default: anjana-data

This step creates the following resources in Kubernetes:

  • anjanadr: docker-registry pull secret to download Anjana Data images

  • anjana-keystore: TLS material: PKCS12 keystore, Java truststore, passwords

  • anjana-secrets: platform credentials decrypted from the AES bundle

To provide your own certificates (bring-your-own):

Bash
export ANJANA_KEYSTORE_P12=/path/to/anjana-ssl.keystore.p12
export ANJANA_CACERTS=/path/to/cacerts
export ANJANA_FULLCHAIN_PEM=/path/to/fullchain.pem
export ANJANA_PRIVKEY_PEM=/path/to/privkey.pem
anjana setup [namespace]

Step 3 - Install the Helm chart

Once the Secrets have been created, install the complete platform with Helm:

Bash
helm dependency build
helm install anjana-platform . -f values-<env>.yaml -n anjana-data

The post-install hook config-init-job automatically fills portuno.app_configuration from anjana-secrets, making all the service configuration available to Horus Config Server.

RECOMMENDED: Once the installation is complete, the Anjana Data frontends can be accessed through the configured domain:

  • https://<dominio>/: Anjana UI Frontend

  • https://<dominio>/configpanel: Portuno UI administration panel

Both frontends listen on the same port 443: anjana-ui and portuno-ui are two independent nginx frontends, separated by path (/ vs /configpanel), not by port. If you need to expose them behind your own on-prem Ingress/LoadBalancer, the separation is done the same way: two path rules pointing to the same port 443 on each Service. See examples/ingress-onprem-traefik/ in the kit repo for a working example with Traefik.

Horus configuration modes

Mode

When to use

Requirement

AWS Secrets Manager

Deployments on AWS EKS

IRSA: ServiceAccount with the eks.amazonaws.com/role-arn annotation

Git

Any environment with access to a Git repository

Private SSH key in a K8s Secret

JDBC (no cloud backend)

On-premises, air-gapped environments, no cloud

anjana-secrets Secret with all connection strings

In all modes, JDBC is always active: horus reads from portuno.app_configuration in addition to the selected cloud backend.


AWS Secrets Manager mode (AWS production)

This is the preconfigured mode in values.yaml for AWS EKS environments.

Prerequisites:

  1. ServiceAccount with IRSA created in the namespace before the helm install:

Bash
kubectl -n <namespace> create serviceaccount horus-dev-sm-sa
kubectl -n <namespace> annotate serviceaccount horus-dev-sm-sa \
  eks.amazonaws.com/role-arn=arn:aws:iam::<AccountID>:role/<RoleName>
  1. Platform secrets loaded into AWS Secrets Manager under the prefix configured in global.horus.aws.prefix (default /secret/dev).

Configuration in values.yaml:

YAML
global:
  horus:
    aws:
      region: "eu-central-1"
      prefix: "/secret/dev"
      serviceAccountName: "horus-dev-sm-sa"
      iamRoleArn: "arn:aws:iam::<AccountID>:role/<RoleName>"
    config:
      cloud: true
      secrets_backend:
        aws_secrets_manager:
          enabled: true
    config_server:
      aws_secrets_manager:
        enabled: true

In this mode anjana-secrets is not necessary (Horus reads from AWS SM via IRSA) and anjana-persistences must be disabled (use managed services: RDS, DocumentDB, ElastiCache, Amazon MQ).


JDBC mode (no cloud backend)

In environments without access to AWS / Azure / GCP, Horus operates in purely JDBC mode: it reads all the configuration from the portuno.app_configuration table in PostgreSQL, which the anjana-config-init Job automatically populates during the helm install.

Step 1 - Prepare credentials

Follow Step 1 of the Anjana Installation - 3 steps section. The keys expected in horus.env are documented in anjana.env.example included in the kit.

Group 1 - persistences.* (used by the config-init job and the init containers of the persistence StatefulSets; not inserted into portuno.app_configuration):

persistences.bbdd.url      host:port/db  (e.g.: postgresql:5432/anjana)
persistences.bbdd.user     PostgreSQL user
persistences.bbdd.pass     PostgreSQL password
persistences.mongodb.user  MongoDB user
persistences.mongodb.pass  MongoDB password
persistences.rabbitmq.user RabbitMQ user
persistences.rabbitmq.pass RabbitMQ password
persistences.valkey.pass   Valkey password
persistences.s3.access_key S3/SeaweedFS access key
persistences.s3.secret_key S3/SeaweedFS secret key

Group 2 - UPPERCASE keys (inserted into portuno.app_configuration and served by Horus to all microservices):

DB_URL                     PostgreSQL host:port (e.g.: postgresql.<namespace>.svc.cluster.local:5432)
DB_USER / DB_PASS          PostgreSQL credentials
MONGO_URI                  mongodb://user:pass@host:27017/db?authSource=admin
RABBITMQ_HOST / RABBITMQ_PORT / RABBITMQ_USER / RABBITMQ_PASS / RABBITMQ_SSL
VALKEY_HOST                Valkey host (e.g.: valkey.<namespace>.svc.cluster.local)
VALKEY_PORT                Valkey port (default: 6379)
VALKEY_PASS                Valkey password
VALKEY_SSL                 true (TLS mandatory in in-cluster deployment)
INDEX_URL                  host:port/solr  (e.g.: solr:8983/solr)
S3_TYPE / S3_URL / S3_REGION / S3_ACCESS_KEY / S3_SECRET_KEY / S3_BUCKET_*
LICENSE_INSTALLATION_CODE / LICENSE_PRIVATE_KEY / LICENSE_PUBLIC_KEY
anjana.domain / anjana.horusAdmin.user / anjana.horusAdmin.pass

The persistences.* keys are consumed directly by the config-init job and the persistence init containers. They are not inserted into portuno.app_configuration.

Step 2 - Run anjana setup
Bash
anjana setup <namespace>

Creates anjana-secrets from the encrypted secrets/anjana.env.aes bundle.

Step 3 - Configure values.yaml
YAML
global:
  horus:
    config:
      cloud: false
      envSecretName: "anjana-secrets"
      secrets_backend:
        aws_secrets_manager:
          enabled: false
    config_server:
      aws_secrets_manager:
        enabled: false
      git:
        enabled: false
Step 4 - Install the chart
Bash
helm install anjana-platform . -f values.yaml -n <namespace> --wait
Behavior of the anjana-config-init Job

The Job is only generated when:

  • global.horus.config.envSecretName is defined (not empty)

  • global.horus.config_server.aws_secrets_manager.enabled: false

  • global.horus.config.secrets_backend.aws_secrets_manager.enabled: false

The Job performs the following operations:

  1. Waits until PostgreSQL is available (pg_isready)

  2. Creates the application schemas (anjana, hermes, minerva, portuno, tot, zeus) if they do not exist; idempotent operation

  3. Creates the portuno.app_configuration table and its sequence if they do not exist

  4. For each key in the Secret:

    • Skips keys with the persistences. prefix and internal Job variables (PGHOST, PGUSER, PGPASSWORD, PGDATABASE)

    • Runs INSERT ... WHERE NOT EXISTS (idempotent: does not overwrite existing values)

This behavior ensures that successive upgrades do not destroy configuration manually customized in the database.

⚠️ Order of operations with backup restoration or sample data

The microservices use optional:configserver: when connecting to Horus. If Horus has no configuration available at startup time (empty table), the service starts with Spring Boot default values and does not automatically reload the configuration once it becomes available.

Recommended order:

  1. Restore the backup or load the sample data into PostgreSQL

  2. Run helm install / helm upgrade

If the deployment was carried out before loading the data:

Bash
kubectl rollout restart deployment -n <namespace>
kubectl rollout restart statefulset/horus -n <namespace>

Git mode

Allows serving configuration from a Git repository.

YAML
global:
  horus:
    config_server:
      aws_secrets_manager:
        enabled: false
      git:
        enabled: true
        uri: "git@github.com:<org>/<repo>.git"
        defaultLabel: "main"
        searchPaths: "{application}"
        privateKeySecret:
          name: "horus-git-key"
          key: "privateKey"

Create the Secret with the SSH key:

Bash
kubectl -n <namespace> create secret generic horus-git-key \
  --from-file=privateKey=~/.ssh/id_rsa \
  --dry-run=client -o yaml | kubectl apply -f -

In-cluster persistences (anjana-persistences)

For on-premise or development/test environments, the kit includes the anjana-persistences subchart with the following persistences, all independently enabled/disabled:

Persistence

Port

Default image

PostgreSQL

5432

postgres:16

MongoDB

27017

mongo:7

RabbitMQ

5672 / 15672

rabbitmq:3.13-management-alpine

Valkey (Redis-compat.)

6379

valkey/valkey:8-alpine

Solr

8983

solr:9.8.1

SeaweedFS (S3-compat.)

8333

chrislusf/seaweedfs:3.79

All persistences read their credentials from the anjana-secrets Secret.

Enable in-cluster persistences in values.yaml:

YAML
anjana-persistences:
  enabled: true        # enables the complete subchart
  postgresql:
    enabled: true
    storage:
      type: hostPath   # hostPath | pvc | emptyDir
      hostPath: /opt/data/postgresql
  mongodb:
    enabled: true
    storage:
      type: hostPath
      hostPath: /opt/data/mongodb
  rabbitmq:
    enabled: true
    storage:
      type: hostPath
      hostPath: /opt/data/rabbitmq
  valkey:
    enabled: true
    storage:
      type: hostPath
      hostPath: /opt/data/valkey
  solr:
    enabled: true
    storage:
      data:
        type: hostPath
        hostPath: /opt/data/solrdata     # Solr index
      config:
        type: hostPath
        hostPath: /opt/data/solrconfig   # ZooKeeper configuration
  seaweedfs:
    enabled: false   # enable if using CDN proxy with in-cluster SeaweedFS
    storage:
      type: pvc
      claimName: seaweedfs-data

For PVC with dynamic provisioning (not hostPath):

YAML
anjana-persistences:
  enabled: true
  postgresql:
    storage:
      type: pvc
      storageClass: "gp3"
      size: 10Gi

Cloud production: keep anjana-persistences.enabled: false and use managed services (RDS, DocumentDB, ElastiCache, Amazon MQ, S3).

CDN Proxy (SeaweedFS / AWS S3)

The anjana-ui frontend includes an nginx proxy to the object storage. It is configured in global.cdnProxy:

YAML
global:
  cdnProxy:
    enabled: true
    mode: aws_s3       # aws_s3 | seaweedfs
    bucket: "anjana-cdn"
    aws_s3:
      region: "eu-central-1"
    seaweedfs:
      endpoint: "https://seaweedfs.<namespace>.svc.cluster.local:8333"

If cdnProxy.enabled: false, the /cdn/ block is omitted from nginx (environments without CDN).


CA Bundle (TLS internal mode only)

Bash
kubectl -n <namespace> create secret generic anjana-internal-ca-bundle \
  --from-file=ca.crt=./ca.crt \
  --dry-run=client -o yaml | kubectl apply -f -

Kit update and maintenance

K8s kit update

To update the kit to a new version:

  1. Download and unzip the new kit into the working directory.

  2. Update the credentials bundle if necessary: anjana horus update --from-json payload.json

  3. Update the Secrets in the cluster: anjana setup [namespace]

  4. Update the chart dependencies and apply the upgrade:

Bash
helm dependency build
helm upgrade anjana-platform . -f values-<env>.yaml -n anjana-data

In case of failure, revert to the previous release:

Bash
helm rollback anjana-platform -n anjana-data

Anjana update and maintenance

Anjana update

The version of each service is controlled through the chart values. To update a specific service, modify its version in the values file and apply the upgrade:

YAML
global:
  version:
    core:
      horus: 6.1.0
      portuno: 5.3.0

From here, there are two strategies for applying the upgrade, depending on the type of change between versions:

Strategy A - with a service window (recommended)

Recommended for cross-minor upgrades (for example 25.x → 26.x) and for any upgrade that includes schema changes, Horus configuration changes, or connection string changes. It guarantees that different versions do not coexist communicating with each other during the transition.

Procedure:

Bash
# 1. Stop core and frontends before the upgrade
kubectl -n anjana-data scale deployment --all --replicas=0
kubectl -n anjana-data scale statefulset/horus --replicas=0

# 2. Wait for the pods to stop
kubectl -n anjana-data get pods -w

# 3. Apply the upgrade
helm upgrade anjana-platform . -f values-<env>.yaml -n anjana-data --wait

RECOMMENDED: announce the maintenance window to users before starting the procedure. After the helm upgrade --wait, the chart automatically brings the services back up in the correct order (horus → core → frontends) thanks to the wait-for-dependencies init container.

Strategy B - rolling (no window, higher risk)

Only recommended for patch upgrades (X.Y.Z → X.Y.Z+1) without structural changes. During the rolling update, different versions may temporarily coexist communicating with each other: check the compatibility matrix in the kit's release notes before proceeding.

Bash
helm upgrade anjana-platform . -f values-<env>.yaml -n anjana-data --wait

IMPORTANT: If the upgrade introduces changes to portuno.app_configuration or to the behavior of Horus Config Server, the services already running will not automatically reload the configuration. In that case it is necessary to force a restart:

Bash
kubectl rollout restart statefulset/horus -n anjana-data
kubectl rollout restart deployment -n anjana-data

Anjana maintenance

Status check

At the pod level:

Bash
kubectl get pods -n anjana-data
kubectl get pods -n anjana-data -w   # real-time watch

RECOMMENDED: use watch mode to quickly identify pods that fail to start or that restart.

During platform startup (initial installation or update), the anjana-ui frontend shows a loading page with the readiness status of the services for the user, and automatically switches to the application when the platform is operational. This makes it possible to know when the environment is ready without needing kubectl access.

For a more detailed check of the service status, the kit exposes three dashboards/endpoints served through Horus and proxied by anjana-ui:

  • Spring Boot Admin: consolidated dashboard of all registered microservices, with health status, logs, JVM metrics, and active configuration:

    https://<dominio>/horus/admin

  • Eureka Dashboard: service registry and Service Discovery status:

    https://<dominio>/horus/balancer

  • Endpoint /actuator/health - granular check per service (requires internal cluster access):

Bash
# Overall health of a service
kubectl exec -n anjana-data <pod> -- curl -k https://localhost:<port>/actuator/health

# Readiness / liveness sub-probes (the same ones Kubernetes uses)
kubectl exec -n anjana-data <pod> -- curl -k https://localhost:<port>/actuator/health/readiness
kubectl exec -n anjana-data <pod> -- curl -k https://localhost:<port>/actuator/health/liveness

RECOMMENDED: Spring Boot Admin is the fastest way to locate the exact point of failure when a service fails to start or goes to DOWN. Eureka Dashboard is used to verify that all services register correctly in the discovery.

Exporting microservice logs

Bash
kubectl logs -n anjana-data <pod> -f
kubectl logs -n anjana-data <pod> --previous   # logs from the previous pod (if it crashed)
kubectl logs -n anjana-data <pod> --tail=200 --since=2h

RECOMMENDED: to provide logs to the Anjana Data support team.

Uninstallation

Uninstallation is performed with Helm:

Bash
# Uninstall the platform (keeps PVCs and Secrets)
helm uninstall anjana-platform -n anjana-data

# Complete namespace deletion (removes ALL resources and data)
kubectl delete namespace anjana-data

IMPORTANT: Deleting the namespace removes all resources. Any data that has not been persisted to external storage will be lost.