A standalone Helm chart for deploying the Doubleword control layer service.
This chart deploys the control layer service, which includes:
- Deployment with configurable replicas
- Service for internal cluster communication
- ConfigMap for application configuration
- Secret for database credentials
- Optional ServiceMonitor for Prometheus metrics
- Kubernetes 1.19+
- Helm 3.0+
helm install my-control-layer oci://ghcr.io/doublewordai/charts/control-layerYou can also provide custom values:
helm install my-control-layer oci://ghcr.io/doublewordai/charts/control-layer -f custom-values.yamlBy default the chart creates <release>-control-layer-secret from
secrets.controlLayer.data. To use a Secret managed by another controller,
configure its name instead:
secrets:
controlLayer:
existingSecret: externally-managed-runtimeThe named Secret must exist in the release namespace and contain
DATABASE_URL plus any other runtime keys required by the deployment. The
chart does not render or mutate a Secret when existingSecret is set, and both
the application and Fusillade workloads consume the same name.
For a small credential that rotates independently, keep the chart-owned Secret and load one or more overlays after it:
secrets:
controlLayer:
extraExistingSecrets:
- rotated-databaseKubernetes resolves duplicate envFrom keys from the later Secret, so overlays
should contain only the keys they intentionally replace.
The following table lists the configurable parameters and their default values. See values.yaml for all available options.
| Parameter | Description | Default |
|---|---|---|
replicaCount |
Number of replicas | 1 |
image.repository |
Container image repository | ghcr.io/doublewordai/control-layer |
image.tag |
Container image tag | Chart appVersion |
image.pullPolicy |
Image pull policy | IfNotPresent |
imagePullSecrets |
Image pull secrets | [] |
| Parameter | Description | Default |
|---|---|---|
service.type |
Kubernetes service type | ClusterIP |
service.port |
Service port | 3001 |
| Parameter | Description | Default |
|---|---|---|
serviceAccountName |
Name of existing service account to use | "" |
If you want to use a specific service account, set serviceAccountName. Otherwise, pods will use the default service account.
| Parameter | Description | Default |
|---|---|---|
secrets.controlLayer.create |
Create secret for database credentials | true |
secrets.controlLayer.name |
Name of existing secret (if not creating) | "" |
secrets.controlLayer.data.DATABASE_URL |
Database connection string | "" (auto-generated if postgresql.enabled) |
postgresql.enabled |
Whether PostgreSQL is enabled in parent chart | true |
secrets.postgres.data.POSTGRES_DB |
PostgreSQL database name | clay |
secrets.postgres.data.POSTGRES_USER |
PostgreSQL user | clay |
secrets.postgres.data.POSTGRES_PASSWORD |
PostgreSQL password | clay_password |
The chart supports both internal and external PostgreSQL databases:
- Internal PostgreSQL: If
postgresql.enabled: true, the DATABASE_URL will be auto-generated using the postgres secret values - External PostgreSQL: Set
secrets.controlLayer.data.DATABASE_URLto your external connection string andpostgresql.enabled: false
| Parameter | Description | Default |
|---|---|---|
serviceMonitor.enabled |
Enable Prometheus ServiceMonitor | false |
serviceMonitor.path |
Metrics endpoint path | /metrics |
serviceMonitor.interval |
Scrape interval | 30s |
serviceMonitor.scrapeTimeout |
Scrape timeout | 10s |
serviceMonitor.labels |
Additional labels for ServiceMonitor | {} |
| Parameter | Description | Default |
|---|---|---|
config |
Control layer application configuration (YAML) | See values.yaml |
env |
Additional environment variables | {} |
| Parameter | Description | Default |
|---|---|---|
resources |
CPU/Memory resource requests/limits | {} |
livenessProbe |
Liveness probe configuration | HTTP GET /healthz |
readinessProbe |
Readiness probe configuration | HTTP GET /healthz |
| Parameter | Description | Default |
|---|---|---|
podAnnotations |
Annotations to add to pods | {} |
podLabels |
Labels to add to pods | {} |
podSecurityContext |
Pod security context | {} |
securityContext |
Container security context | {} |
nodeSelector |
Node selector | {} |
tolerations |
Tolerations | [] |
affinity |
Affinity rules | {} |
volumes |
Additional volumes | [] |
volumeMounts |
Additional volume mounts | [] |
The fusillade daemon handles background batch processing tasks. By default, it runs within the control layer pods based on leader election. You can optionally deploy it as a separate deployment for better resource isolation and independent scaling.
| Parameter | Description | Default |
|---|---|---|
fusillade.enabled |
Deploy fusillade as a separate deployment | false |
fusillade.replicaCount |
Number of fusillade replicas | 1 |
fusillade.mode |
Optional daemon mode for the standard fusillade deployment (both, request_only, batch_only); empty uses the application default |
"" |
fusillade.split.enabled |
Render separate request-only and batch-only daemon deployments | false |
fusillade.split.request.enabled |
Render the request-only daemon deployment | true |
fusillade.split.request.replicaCount |
Number of request daemon replicas | inherits fusillade.replicaCount |
fusillade.split.request.resources |
CPU/Memory requests/limits for request daemon pods | inherits fusillade.resources |
fusillade.split.request.env |
Additional environment variables for request daemon pods | merged after env and fusillade.env |
fusillade.split.request.database |
Database pool overrides for request daemon pods | merged over fusillade.database |
fusillade.split.batch.enabled |
Render the batch-only daemon deployment | true |
fusillade.split.batch.replicaCount |
Number of batch daemon replicas | inherits fusillade.replicaCount |
fusillade.split.batch.resources |
CPU/Memory requests/limits for batch daemon pods | inherits fusillade.resources |
fusillade.split.batch.env |
Additional environment variables for batch daemon pods | merged after env and fusillade.env |
fusillade.split.batch.database |
Database pool overrides for batch daemon pods | merged over fusillade.database |
fusillade.image.repository |
Override image repository | (uses main image) |
fusillade.image.tag |
Override image tag | (uses main image) |
fusillade.resources |
CPU/Memory resource requests/limits | {} |
fusillade.podAnnotations |
Annotations for fusillade pods | {} |
fusillade.podLabels |
Labels for fusillade pods | {} |
fusillade.nodeSelector |
Node selector for fusillade pods | {} |
fusillade.tolerations |
Tolerations for fusillade pods | [] |
fusillade.affinity |
Affinity rules for fusillade pods | {} |
fusillade.env |
Additional environment variables | {} |
When fusillade.enabled: true:
- The control layer pods will have
background_services.batch_daemon.enabledset tonever - The fusillade pods will have
background_services.batch_daemon.enabledset toalways - The standard fusillade deployment uses the application's default daemon mode unless
fusillade.modeis set - With
fusillade.split.enabled: true, request pods usemode=request_onlyand batch pods usemode=batch_only
The internal keystore can create and use a dedicated StorageClass for its Redis
PVC. On GKE, set disk-encryption-kms-key to provision the backing Persistent
Disk with a customer-managed Cloud KMS key:
keystore:
enabled: true
persistence:
managedStorageClass:
enabled: true
parameters:
type: pd-balanced
disk-encryption-kms-key: projects/PROJECT_ID/locations/REGION/keyRings/KEY_RING/cryptoKeys/KEYExisting PVCs keep the StorageClass they were created with. Recreate or migrate the keystore volume to move an existing install onto the managed StorageClass.
# custom-values.yaml
replicaCount: 2
image:
tag: "v1.2.3"
postgresql:
enabled: false
secrets:
controlLayer:
create: true
data:
DATABASE_URL: "postgres://user:[email protected]:5432/controldb"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi# monitoring-values.yaml
serviceMonitor:
enabled: true
interval: 15s
labels:
prometheus: kube-prometheus# fusillade-values.yaml
replicaCount: 3
fusillade:
enabled: true
replicaCount: 2
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi# split-fusillade-values.yaml
fusillade:
enabled: true
split:
enabled: true
request:
replicaCount: 4
resources:
requests:
cpu: 500m
memory: 512Mi
batch:
replicaCount: 1
resources:
requests:
cpu: 1000m
memory: 1Gi
database:
fusillade_pool:
max_connections: 80This project is licensed under the Apache License 2.0 - see the LICENSE.md file for details.