Production Planning
A production installation of Catalyst Enterprise Self-Hosted runs on a dedicated Kubernetes cluster with access to external PostgreSQL and, optionally, Kafka. Use the guidance below to size your infrastructure for the expected workload.
Cluster requirements
- Kubernetes 1.24 or later, installed in a dedicated cluster.
- A CNI that supports
NetworkPolicyresources. - At least 3 worker nodes for production, to support High Availability of the system components.
- Outbound connectivity to the Diagrid Cloud endpoints listed in Architecture.
Reference sizing profiles
The following profiles are verified minimum configurations for running Catalyst. Pick the profile that best matches your workload; individual components can be scaled independently from there.
| Profile | Use case | Kubernetes worker nodes | AWS equivalent | Workflow PostgreSQL | AWS equivalent |
|---|---|---|---|---|---|
| Dev / PoC | Evaluation and development. Not suitable for production. | 2 × (2 vCPU, 4 GiB) | c5.large | 2 vCPU, 4 GiB | db.t3.medium |
| Small production | Low-volume workflows. | 3 × (8 vCPU, 16 GiB) | c5.2xlarge | 4 vCPU, 16 GiB | db.m5.xlarge |
| Large production | High-volume workflows with independent scheduler state. | 3 or more × (8 vCPU, 16 GiB) | c5.2xlarge | 16 vCPU, 64 GiB | db.m5.4xlarge |
For the Large profile, we recommend a separate PostgreSQL instance for the Dapr Scheduler (8 vCPU / 32 GiB, e.g. AWS db.m5.2xlarge) so that scheduler state does not contend with workflow state.
Component resource footprint
The following are the default resource requests and limits shipped with the Catalyst Helm chart. Use them to validate that your node pool has sufficient capacity. All values can be tuned via Helm; refer to the Helm Reference for the full set of options.
| Component | Replicas | CPU (request / limit) | Memory (request / limit) |
|---|---|---|---|
| Agent | 1 | 40m / — | 500Mi / 1200Mi |
| Management | 2 | 40m / — | 100Mi / 1200Mi |
| Gateway (Envoy) | 1 (2 with HA) | 100m / 1000m | 512Mi / 2048Mi |
| Gateway (Control Plane) | 1 (2 with HA) | 50m / 100m | 50Mi / 100Mi |
| Identity Injector | 1 | 50m / 200m | 64Mi / 128Mi |
| Dapr Server (per app) | per app | 10m / 300m | 25Mi / 256Mi |
| OpenTelemetry Collector (metrics) | per project | 75m / — | 500Mi / 1100Mi |
| OpenTelemetry Collector (logs) | per project | 50m / — | 500Mi / 750Mi |
| Dapr Scheduler | 1 (3 with HA) | — / — | 130Mi / 175Mi |
| Dapr Sentry | 1 | — / — | — / 100Mi |
Dapr Servers and OpenTelemetry Collectors are provisioned per app and Project at runtime, so their aggregate footprint scales with the number of Apps and Projects you deploy.
Scale limits
Each Catalyst installation enforces the following internal limits to prevent resource exhaustion:
| Limit | Default | Helm value |
|---|---|---|
| Projects per installation | 50 | agent.config.placement.max_project_count |
| Apps per installation | 300 | agent.config.placement.max_appid_count |
High availability
We recommend enabling High Availability for production installations. Set gateway.ha.enabled: true in your Helm values to run two replicas of the Gateway Envoy and Control Plane. The Management service runs two replicas by default. See the Helm Reference for the full set of HA-related options.
We also recommend running external dependencies in a Multi-AZ configuration:
- Workflow PostgreSQL.
- Dapr Scheduler PostgreSQL, when using the Large profile.
- Kafka, when Managed Pub/Sub is enabled.
Refer to the Helm Reference for configuration of external PostgreSQL and Kafka instances.
Under load
The Helm chart defaults are tuned for typical non-production workloads. High-throughput deployments — many concurrent workflows, frequent reminders and jobs, or large bursts of application or agent activity — surface bottlenecks that the defaults do not anticipate. The guidance below comes from load testing the Large production profile and documents the adjustments that had the largest impact, giving recommendations so you can apply them before hitting the same limits.
The settings below are independent — apply the ones that match the pressure you observe.
Create a dedicated database for the Scheduler service
The Dapr Scheduler manages reminders, jobs, and workflow timers, and is backed by PostgreSQL by default. Out of the box it reuses the global PostgreSQL instance configured under global.postgresql (postgresql.use_global: true) in the Helm chart and shares a database server with the workflow state store. For high job and reminder volume, give it a dedicated PostgreSQL instance so its writes do not contend with the workflow state store: set postgresql.use_global: false and provide the connection yourself. It is also recommended to raise the database connection pool well above the default of 5 connections per instance, and increase the worker count that drains the job queue. See the following Helm values as an example:
agent:
config:
internal_dapr:
scheduler:
backend_type: postgresql
postgresql:
use_global: false
max_conns_per_instance: 100
connections:
- existing_secret_name: scheduler-external-postgresql
existing_secret_namespace: cra-agent
workers: 8192
scheduler_resources:
requests:
memory: 256Mi
limits:
memory: 1Gi
The referenced Kubernetes secret must contain the keys host, port, username, password, and database. The PostgreSQL scheduler uses logical replication, so the external database backing it must be configured with wal_level = logical — managed offerings ship with it disabled (Amazon RDS/Aurora: rds.logical_replication = 1, Cloud SQL: cloudsql.logical_decoding = on, Azure Flexible Server: wal_level = logical).
Running the scheduler on its own instance, separate from the workflow store, keeps the two from contending — see the dedicated scheduler instance in the Large production profile. The default scheduler memory limit of 175Mi is also too low for this volume; the values above raise the request to 256Mi and the limit to 1Gi.
Tune the workflow state store
Workflow execution state is persisted to PostgreSQL, configured under global.postgresql in the Helm chart. For production installations use an external PostgreSQL instance, give the database a connection pool sized for the number of concurrent workflows — well above the chart default of 2 connections — and enable write batching:
global:
postgresql:
create: false
external:
existing_secret_name: catalyst-external-postgresql
existing_secret_namespace: cra-agent
max_conns: 100
agent:
config:
project:
workflow_store_batch_size: 100
workflow_store_flush_interval: 100ms
workflow_store_batch_size and workflow_store_flush_interval control how workflow execution updates are written to PostgreSQL. By default each update is written eagerly, which generates a write per state transition. Batching coalesces up to workflow_store_batch_size updates, flushing whenever the batch fills or workflow_store_flush_interval elapses, whichever comes first. This trades a small amount of write latency for a large reduction in database write pressure — the single most effective change for sustaining high workflow throughput.
Scale and resource the sidecars
Each App ID runs a Dapr sidecar. The default CPU request of 10m is sized for idle sidecars and starves the runtime under load. Raise the request to a full CPU, enable autoscaling so busy App IDs add replicas, and lower the log level to cut logging overhead on the hot path:
agent:
config:
sidecar:
log_level: error
autoscaling:
enabled: true
min_replicas: 1
max_replicas: 10
target_cpu_utilization_percentage: 80
resources:
requests:
cpu: 1
memory: 50Mi
soft_mem_limit:
override: "0"
Setting soft_mem_limit.override: "0" disables the Go soft memory limit (GOMEMLIMIT) while keeping the container memory limit in place. The soft limit forces the garbage collector to run more aggressively as memory approaches the cap, which adds CPU overhead under sustained load. Disabling it favors throughput, at the cost of the sidecar being more likely to be OOM-killed if it exceeds its memory limit — size resources.limits.memory accordingly.
Enabling High availability also matters under load: the gateway is the first component to saturate as request volume grows, and running at least two Envoy replicas removes both the throughput ceiling and the single point of failure on the request path.
Private container images
If you are installing in an environment without access to public container registries or prefer to use your own container registry, you can pull the artifacts from our public registry, re-tag them, and push them to your private registry. Then, you can configure the Helm chart to use your private registry by setting the appropriate values. See the Helm Reference for the chart values.
We have provided a script and documentation on how to achieve this in the Catalyst Enterprise Self-Hosted Helm Chart repository.
Next steps
- AWS Deployment — reference architecture on AWS (VPC, EKS, RDS, Bastion host).
- Azure Deployment — reference architecture on Azure (VNet, AKS, Azure Firewall, management VM).