Twenty
Twenty CRM with its native server and worker, a private initial workspace, PostgreSQL, authenticated Redis and retained encryption identity.
Operating contract
- Official
twentycrm/twenty:v2.39.0image pinned by immutable manifest digest. - One Recreate Pod serializes migrations and shares local files between server and worker. This chart does not claim application HA or horizontal scaling.
- Native first-administrator and workspace activation before public startup, followed by explicit closure of public workspace invite links.
- PostgreSQL and persistent noeviction Redis through HelmForge dependencies, or existing services with native TLS and optional private CA trust.
- UID 1000, read-only application image layers, dropped Linux capabilities, tokenless application/helper Pods and explicit temporary volumes.
- Native attachments backed by local storage or a private S3 bucket, with the same storage available to native SDK-generation jobs.
- Authenticated implicit-TLS SMTP for actual native password recovery.
- Private native server metrics, ServiceMonitor and target-availability rules. The co-located worker has separate health checks and job acceptance; it does not share the server’s fixed Prometheus port.
- Existing credential Secrets, External Secrets Operator, Ingress with an explicit class, canonical Gateway API routes and dual-stack Services.
Installation
helm repo add helmforge https://repo.helmforge.dev
helm upgrade --install crm helmforge/twenty \
--namespace crm --create-namespace \
--set server.publicUrl=https://crm.example.com \
--set [email protected] \
--set bootstrap.existingSecret=crm-initial-owner
Create that Secret in the namespace with the user-password key before installing. Configure HTTPS exposure and an
enforcing CNI with trusted namespace policy ownership. Set explicit NetworkPolicy peers for ingress and external
integrations. An ordinary readiness probe cannot protect first-administrator enrollment.
Use the production example as a deployment-specific starting point. Hostnames, Secret references and example network ranges require replacement. The example demonstrates external services; the default topology uses the HelmForge PostgreSQL and Redis dependencies.
Credentials and enrollment
Retain ENCRYPTION_KEY and SERVER_ID, the ownership marker, database, files and Redis queue state together. The
generated encryption key encodes 32 random bytes; the server identity is a UUID v4. Rotating the bootstrap password
Secret does not reset an existing native password. Native email/password changes remain native.
Existing accounts without the matching marker and incompatible retained keys are refused before migration. The chart does not silently adopt an unrelated database or rewrite administrator privileges through SQL. See private enrollment and recovery.
Integration boundaries
SMTP uses native port 465 because other ports would use opportunistic STARTTLS in this upstream driver. With SMTP disabled, the chart selects a refused loopback SMTP transport instead of the upstream token-printing LOGGER driver. Native recovery requests can still acknowledge submission without delivery. Enable SMTP and verify receipt before relying on recovery or invitations.
S3 stores application files and generated SDK archives; it is not a backup policy or permission to scale the application. Presigned browser-to-bucket transfer is disabled, keeping the application file proxy as the browser-facing endpoint. Signed application download links remain temporary bearer capabilities.
The supported contract keeps native configuration environment-owned. Database configuration overrides are disabled; workspace records and permissions remain native database state. OIDC/SSO licensing, third-party account synchronization, AI providers and hosted function execution are outside the tested integration set.
See dependencies, storage, SMTP and observability.
Validation
Behavioral acceptance passed native administrator/workspace enrollment, closed uninvited signup without a new database user, company creation, signed attachment transfer with exact download bytes, actual SDK job completion and retained session, identity and company after Pod replacement. An intentionally exposed native port must cause the isolation admission check to reject startup.
The integration profiles passed actual HTTPS browser login with a Secure HttpOnly session, external PostgreSQL and Redis TLS with negative controls, private S3 bytes, delivered SMTP recovery with one-use tokens and preserved password changes, real Prometheus collection and denied unauthorized scrape traffic. The production profile verifies tokenless application, helper, PostgreSQL and Redis Pods.
Coordinated recovery restores into a fresh database and fresh application/Redis PVCs, preserves the original session and attachment, and requires a previously pending native job to execute and rewrite its SDK archive afterward.
Run make validate-chart CHART=twenty against each release candidate. The complete
gate includes all CI profiles, static checks and behavioral acceptance; individual
diagnostic profiles do not replace that gate.
Security Scan: twenty
| Framework | Score |
|---|---|
| Overall | 99.12% |
| MITRE | 99.26% |
| NSA | 98.54% |
| SOC2 | 97.50% |
Kubescape 4.0.13, default rendered manifests, 2026-09-11. No controls were suppressed. C-0012 matches the literal Bearer authorization construction in the initializer’s ConfigMap code; it does not contain a credential. C-0034 identifies the released Redis dependency’s missing Pod-level token-automount field. Redis has a dedicated account without RBAC grants and denied egress. The production profile shares an explicitly named tokenless account with the dependencies and checks every Pod for projected API tokens. See dependency hardening. This manifest scan does not replace application review or image vulnerability management.
dependencies details
PostgreSQL
The default HelmForge dependency creates the dedicated twenty database and non-superuser application role. Its
initialization grants CREATE on that database so native installation can create core and workspace schemas. Native setup
also requires uuid-ossp, unaccent, citext and the immutable unaccent wrapper. The MVP proved these operations
without granting a superuser role or CREATEDB.
For an existing service, disable postgresql.enabled, set database.host, name, username and password Secret. The
Secret key defaults to password. Keep TLS enabled and use a certificate-matching hostname. A CA Secret supplies
additional trust through the native PostgreSQL connection’s verify-full configuration. The initializer and regular
native processes use the same encoded credentials.
Provision database ownership and schema privileges deliberately before first startup. Native migrations manage schemas; the chart does not disable migrations to make an incompatible database appear healthy. Initialization checks native command exit codes and relevant private error output because some upstream commands can log an error while returning success.
Redis
The default HelmForge Redis dependency uses authentication, standalone storage, append-only persistence and
maxmemory-policy noeviction. Redis holds native cache, PubSub and BullMQ job state; treating it as an expendable cache
loses work. Size memory and disk for queue depth, retention and application caches.
The pinned Redis 2.0.1 dependency requires its default port 6379 for health probes. The chart rejects a custom bundled port; external Redis supports a custom port.
For external Redis, disable redis.enabled and configure cache.host, password Secret and optional username/database
index. TLS uses rediss://; a CA Secret extends trust for native server, worker and health checks. Authentication
remains required. Native queue and cache clients share this configured endpoint.
The tested ACL identity supports the native cache, PubSub, scripting and BullMQ commands. This is not proof of an arbitrary restrictive ACL policy. Managed services must support the native command set and retain noeviction semantics.
The external-dependencies acceptance profile passed with the native PostgreSQL and ioredis clients, including certificate-chain and hostname rejection, Redis wrong-password rejection and successful controls afterward. Native company creation, attachment bytes, SDK worker output and retained identity/session checks also passed on that TLS topology.
Network and Kubernetes identity
Bundled dependency peers are selected by release labels. External services need explicit networkPolicy.extraEgress
destinations and ports, including endpoints reached behind a Kubernetes Service. CA trust does not replace network
policy.
The released Redis dependency does not expose a Pod-level token-automount option. Use one explicitly named tokenless
ServiceAccount shared with the dependency when hardening a complete deployment; ci/production-values.yaml demonstrates
that configuration for PostgreSQL and Redis. The acceptance profile checks all four application/dependency Pods for
absence of projected API tokens.
observability details
metrics.enabled enables the upstream Prometheus driver on the server’s fixed port 9464. A private Service, optional
ServiceMonitor and optional PrometheusRule expose that native endpoint only to configured NetworkPolicy scrape peers.
The public application proxy does not forward requests to this metrics port.
Server and worker share a Pod for local-file consistency. The native Prometheus port cannot be configured; enabling it in both processes would cause a bind conflict. This ServiceMonitor therefore covers the server only. The worker uses native process, authenticated Redis and BullMQ registration checks, with actual SDK job completion in behavioral acceptance. A probe is not a Prometheus metric.
The metrics profile requires real Prometheus up=1, a loaded target-availability rule and a native successful GraphQL
operation counter reflecting CRM traffic. An unrelated Pod must fail to reach metrics while retaining access to public
application health. This runtime profile passed all these checks, including the
retained company, session and attachment after Pod replacement.
Use the native server/worker logs for job failures and startup diagnostics. Private bootstrap output is stored separately and deleted before successful public startup. Keep authorized access to those diagnostics; they may contain secrets.
The chart does not ship an OTLP collector or claim worker metrics coverage. Native worker OTLP export needs an independently validated collector pipeline, including DELTA-to-cumulative conversion before Prometheus counters are exposed.
onboarding details
The initial behavioral gate verified private enrollment, closed signup and retained identity. The complete production integration gate is tracked in the chart README.
Twenty binds its native listener to unspecified addresses. A readiness probe or missing Service endpoints cannot isolate it during first-administrator creation. The chart requires an enforcing CNI and trusted namespace policy ownership. Its separate HMAC helper probes the caller’s fixed native and control ports for every Pod address family before any private native listener starts. These bounded observations do not prove universal denial against additive policies, untrusted controllers, injected containers or node/cluster administrators.
After database admission, the initializer runs the native initialization and upgrade commands serially. It checks process exit status, private command output and the SQL prerequisites that native setup can fail to create while returning success. Cache flushes require their native completion marker. Native cache namespaces are separate from retained BullMQ queue data.
An empty instance receives the configured administrator through native signUp. The native workspace flow creates and
activates the workspace, grants the initial administrator privileges and disables public invite links through
updateWorkspace. Public invites default to enabled upstream, so workspace creation alone is not a sufficient
private-installation check. No SQL password or privilege writes are used.
Native activation in 2.39.0 also creates demonstration companies, people, workflows, opportunities and dashboards. The upstream activation API does not offer a switch to skip this prefill. Review and remove unwanted examples through Twenty before importing production records or enabling external integrations. Startup never deletes workspace records automatically.
The initializer writes a pending ownership marker before enrollment to permit recovery from a process failure between account and workspace creation. That pending flow can use the initial credentials to finish native enrollment. Once ownership is complete, future startup follows the retained native user ID, workspace membership and encryption fingerprint. It does not replay the original password or revert native email/password changes.
Existing users without the matching marker are not silently adopted. Missing administrator privileges or membership, changed encryption identity, incomplete workspace activation or re-enabled public invite links block chart-managed startup. Migrate ownership deliberately before adopting an existing installation.
The native administrator configuration is environment-owned through IS_CONFIG_VARIABLES_IN_DB_ENABLED=false. The
configuration UI becomes read-only; export effective database configuration before migrating to this contract. Native
workspace permissions remain database state and are checked separately.
Private command output uses a mode-0600 diagnostic file and is deleted before successful public startup. Authorized operators can inspect it after a failed initialization. It can contain sensitive application diagnostics and must not be copied into public issue reports without redaction.
retained-state details
Retain the native ENCRYPTION_KEY and SERVER_ID, PostgreSQL database, application PVC and persistent Redis queue as
one recovery set. With S3 enabled, preserve the corresponding bucket recovery point as well. The PVC’s hidden ownership
marker contains IDs and a key fingerprint; it does not replace a backup of the Secret itself.
The application uses one Recreate Pod. Initialization admits the retained key identity before migrations, runs native upgrade/cache/cron commands sequentially and verifies administrator membership and closed public invite links before reopening the public listener. Replacing the bootstrap password Secret does not rotate native credentials or enrollment identity.
Quiesce both native server and worker before collecting database and file snapshots. Redis contains pending and scheduled work, so include a consistent Redis persistence snapshot after clients are quiesced and Redis shuts down cleanly. Copy dotfiles, preserve volume ownership and restore only into empty destinations. Reopen the application after every component and key is present.
The implemented restore acceptance creates a delayed native SDK-generation job, quiesces clients, dumps PostgreSQL and copies application and Redis persistence. It restores into a fresh database and two new PVCs, then requires that pending job to complete through the native worker and write its SDK archive again. Original session, company, attachment bytes and identity must also survive. This acceptance passed with a fresh database and two fresh PVCs. The restored pending job completed and rewrote its native SDK ZIP after recovery; its restored queue entry alone was not considered proof of worker functionality.
A database-only restore does not prove files or pending jobs were recovered. Queue reinitialization is not a substitute for recovering accepted work. The local fixture does not prove cross-region S3 recovery or simultaneous active instances against the same data.
Before upgrading, preserve a recovery set and validate native migrations against a restored copy. Legacy APP_SECRET installations and key rotation require the upstream migration procedure and deliberate ownership-marker reconciliation; the chart does not silently adopt or rewrite a legacy database. Never change the encryption Secret merely to resolve a startup admission failure.
smtp details
Enable SMTP with a certificate-matching host, sender address, authentication username and existing password Secret.
smtp.from is the sender address, without a display-name wrapper. The native display name is Twenty. Native port 465 is
required for implicit TLS; a Kubernetes Service can map it to an unprivileged backend listener. The chart does not
expose opportunistic or disabled TLS modes.
The initializer verifies TLS and authentication before starting the native enrollment flow. A private CA Secret can extend trust without disabling certificate or hostname checks. Additional CA trust is shared by native Node integrations.
Upstream defaults to a LOGGER mail driver that includes recovery links in logs. When SMTP is disabled, this chart
instead selects the SMTP driver against a refused loopback listener. No delivery is available in that mode. The upstream
GraphQL mutation can acknowledge a request before asynchronous delivery fails; an HTTP 200 or success: true is not
proof that a message was delivered.
Enable actual SMTP before depending on password recovery or invitations. The acceptance profile checks the received sender, recipient and authenticated SMTP identity, follows the delivered native reset token, verifies single-use behavior, rejects the old password and authenticates with the recovered password. The new native password must survive Pod replacement while the initial Secret stays unchanged. This acceptance passed, including authenticated delivery, wrong credentials, untrusted CA and a resolvable hostname absent from the certificate. The original post-reset session, company, attachment and retained identity also survived replacement.
The chart never changes the native GraphQL bundle or attempts to authorize GraphQL mutations using a proxy regular expression. Configuration and native account permissions remain distinct controls.
storage details
Local storage lives at /app/data/storage on the retained application PVC. Server and worker share one Pod so they can
safely share an ordinary RWO claim. Temporary SDK generation uses a bounded writable /tmp; the image remains
read-only. The generated frontend configuration has a separate writable file.
storage.driver=s3 configures the native S3 driver on both server and worker. Use an existing private bucket, signing
region and credential Secret. The default keys are access-key-id and secret-access-key. An optional compatible
endpoint must use HTTPS. Native upstream addressing is path-style.
An additional CA bundle applies before native Node startup. Certificate and hostname verification remain enabled. The integration acceptance compares bytes from a native attachment against the actual stored object, requires unsigned access to fail and checks incorrect CA/hostname rejection. It also requires the native worker’s SDK ZIP in the same bucket, with its database checksum recorded. This acceptance passed, including the original session, company and exact attachment bytes after Pod replacement.
Browser transfers use native signed application endpoints, not a private cluster bucket hostname. The chart disables native presigned direct-to-bucket transfers. A signed application download URL is intentionally a time-limited capability; possession can permit download without a separate login until it expires.
The PVC still stores .helmforge-identity.json with S3 enabled. Preserve it and the native key Secret with database and
bucket recovery points. S3 storage is not an S3 backup schedule; configure independent bucket protection and retention.
The fresh-PVC restore profile proves local storage recovery, not S3 disaster recovery.
Production example
# SPDX-License-Identifier: Apache-2.0
# Replace all example hostnames, Secrets and documentation network ranges.
server:
publicUrl: https://crm.example.com
bootstrap:
email: [email protected]
existingSecret: crm-initial-owner
identity:
existingSecret: crm-native-identity
persistence:
size: 20Gi
postgresql:
enabled: false
redis:
enabled: false
database:
host: postgres.example.com
name: twenty
username: twenty
passwordSecret: crm-postgresql
tls:
enabled: true
caSecret: database-ca
cache:
host: redis.example.com
passwordSecret: crm-redis
tls:
enabled: true
caSecret: redis-ca
smtp:
enabled: true
host: smtp.example.com
port: 465
from: [email protected]
username: crm
existingSecret: crm-smtp
ingress:
enabled: true
ingressClassName: nginx
hosts:
- host: crm.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: crm-https
hosts: [crm.example.com]
networkPolicy:
ingressFrom:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
extraEgress:
- to:
- ipBlock: { cidr: 192.0.2.10/32 }
ports: [{ protocol: TCP, port: 5432 }]
- to:
- ipBlock: { cidr: 192.0.2.11/32 }
ports: [{ protocol: TCP, port: 6379 }]
- to:
- ipBlock: { cidr: 192.0.2.12/32 }
ports: [{ protocol: TCP, port: 465 }]
Complete values
# SPDX-License-Identifier: Apache-2.0
# -- Override the application name used in workload selectors.
nameOverride: ''
# -- Override the generated application resource name.
fullnameOverride: ''
# -- Additional resource labels; chart ownership labels are protected.
commonLabels: {}
# -- One Recreate Pod serializes migrations and shares retained files with its native worker.
replicaCount: 1
# -- Registry Secret references for pulling mirrored official images.
imagePullSecrets: []
# -- Additional native environment; chart-owned identity, policy and dependency settings are rejected.
extraEnv: []
# -- Tokenless identity for the application and network admission helper.
serviceAccount:
# -- Create this Kubernetes resource.
create: true
# -- Explicit name for this setting.
name: ''
# -- Kubernetes annotations for this resource.
annotations: {}
# -- Keep false for workloads that do not use the Kubernetes API.
automountServiceAccountToken: false
# -- Public proxy Service; native port 3010 is never exposed here.
service:
# -- Kubernetes Service exposure type; prefer ClusterIP behind an HTTPS controller.
type: ClusterIP
# -- TCP port.
port: 3000
# -- Kubernetes annotations for this resource.
annotations: {}
# -- Optional Kubernetes address-family policy; empty preserves cluster defaults.
ipFamilyPolicy: ''
# -- Optional ordered IPv4/IPv6 families supported by the cluster.
ipFamilies: []
# -- Optional public Ingress with an explicit controller class.
ingress:
# -- Enable this capability.
enabled: false
# -- Explicit Ingress controller class when Ingress exposure is enabled.
ingressClassName: ''
# -- Kubernetes annotations for this resource.
annotations: {}
# -- Public hostnames and paths routed to the application proxy.
hosts: []
# -- Controller TLS Secret references for the public hostnames.
tls: []
# -- Canonical Gateway API HTTPRoutes to an existing controller.
gatewayAPI:
# -- Enable this capability.
enabled: false
# -- Canonical route items with parentRefs, hostnames and routing rules.
httpRoutes: []
# -- External Secrets Operator items that materialize referenced native credential Secrets.
externalSecrets:
# -- Enable this capability.
enabled: false
# -- Default interval for reconciling provider Secrets.
refreshInterval: 1h
# -- ExternalSecret objects that materialize native credential and identity references.
items: []
# -- Native server startup, dependency readiness and listener liveness.
probes:
# -- Allow native startup to finish before readiness and liveness apply.
startup:
# -- Consecutive failed probes before this probe changes workload health.
failureThreshold: 60
# -- Seconds between probe attempts.
periodSeconds: 5
# -- Maximum seconds allowed for each probe attempt.
timeoutSeconds: 5
# -- Native health endpoint checks application dependencies before traffic is admitted.
readiness:
# -- Consecutive failed probes before this probe changes workload health.
failureThreshold: 3
# -- Seconds between probe attempts.
periodSeconds: 10
# -- Maximum seconds allowed for each probe attempt.
timeoutSeconds: 5
# -- Listener checks detect a stopped native server without restarting it for dependency outages.
liveness:
# -- Consecutive failed probes before this probe changes workload health.
failureThreshold: 3
# -- Seconds between probe attempts.
periodSeconds: 20
# -- Maximum seconds allowed for each probe attempt.
timeoutSeconds: 5
# -- Native server and private initializer CPU and memory allocation.
resources:
# -- Resources reserved by the scheduler.
requests:
# -- Kubernetes CPU quantity.
cpu: 250m
# -- Kubernetes memory quantity.
memory: 1Gi
# -- Maximum resources available to this container.
limits:
# -- Kubernetes CPU quantity.
cpu: '2'
# -- Kubernetes memory quantity.
memory: 3Gi
# -- Non-root UID/GID 1000 with ownership for retained volumes.
podSecurityContext:
# -- Require a non-root runtime user.
runAsNonRoot: true
# -- Numeric runtime user ID.
runAsUser: 1000
# -- Numeric runtime group ID.
runAsGroup: 1000
# -- Supplementary group for mounted volume access.
fsGroup: 1000
# -- Volume ownership reconciliation policy.
fsGroupChangePolicy: OnRootMismatch
# -- System call filtering profile.
seccompProfile:
# -- Use the container runtime default system-call filter.
type: RuntimeDefault
# -- Read-only image filesystem, no privilege escalation and dropped Linux capabilities.
securityContext:
# -- allow Privilege Escalation configuration.
allowPrivilegeEscalation: false
# -- Keep image layers read-only; use explicit writable volumes.
readOnlyRootFilesystem: true
# -- Linux process capability configuration.
capabilities:
# -- Linux capabilities removed from the container.
drop:
- ALL
# -- Additional workload labels; preserve selector ownership.
podLabels: {}
# -- Additional workload annotations.
podAnnotations: {}
# -- Node labels required for scheduling.
nodeSelector: {}
# -- Node taints tolerated by the workloads.
tolerations: []
# -- Application Pod scheduling affinity.
affinity: {}
# -- Topology constraints for the supported single application Pod.
topologySpreadConstraints: []
# -- Optional application PriorityClass.
priorityClassName: ''
# -- Grace period for native server and worker shutdown.
terminationGracePeriodSeconds: 60
# -- Required shared native files and retained server/administrator identity marker.
persistence:
# -- Enable this capability.
enabled: true
# -- Use an existing persistent volume claim.
existingClaim: ''
# -- StorageClass name; empty uses the cluster default.
storageClass: ''
# -- Persistent storage capacity.
size: 10Gi
# -- Persistent volume access modes.
accessModes:
- ReadWriteOnce
# -- Retain this PVC when the Helm release is uninstalled.
retain: true
# -- Kubernetes annotations for this resource.
annotations: {}
# -- Bounded writable space for native SDK generation and temporary files.
runtime:
# -- Bounded temporary space for native commands and generated SDK archives.
temporarySize: 1Gi
# -- Official Twenty image pinned to an immutable release digest.
image:
# -- Official container image repository.
repository: docker.io/twentycrm/twenty
# -- Pinned upstream image tag or immutable digest.
tag: v2.39.0@sha256:aaa79085ff93049e074a5725a1f7689298ac0a07aedac07830e2bb45665d6caf
# -- Kubernetes image pull policy.
pullPolicy: IfNotPresent
# -- Native public origin and fixed proxy port.
server:
# -- TCP port.
port: 3000
# -- Stable public HTTPS origin used for workspace authentication.
publicUrl: ''
# -- Private native administrator and initial workspace enrollment.
bootstrap:
# -- Initial native administrator email address.
email: [email protected]
# -- Display name for the privately initialized workspace.
workspaceName: HelmForge CRM
# -- Optional initial password; prefer an existing Secret.
password: ''
# -- Existing Secret name; keys must match the configured native contract.
existingSecret: ''
# -- Secret key containing the password.
passwordKey: user-password
# -- Required enforcing ingress and egress policies; trust namespace policy owners.
networkPolicy:
# -- Enable this capability.
enabled: true
# -- Trusted ingress peers for public port 3000; empty permits same-namespace Pods.
ingressFrom: []
# -- Keep true for private network admission.
egressIsolation: true
# -- Permit public HTTPS destinations while excluding private and special-use ranges.
allowPublicHttps: false
# -- DNS resolver peers permitted by the application egress policy.
dnsEgress:
- namespaceSelector:
# -- Labels that select the permitted network peer.
matchLabels:
kubernetes.io/metadata.name: kube-system
# -- Pod labels that select the permitted network peer.
podSelector:
# -- Labels that select the permitted network peer.
matchLabels:
# -- CoreDNS workload label used by the default resolver policy.
k8s-app: kube-dns
# -- Additional destination peers and ports for explicitly configured dependencies.
extraEgress: []
# -- External PostgreSQL configuration; disable the bundled dependency when used.
database:
# -- External service hostname matching the configured certificate.
host: ''
# -- TCP port.
port: 5432
# -- Explicit name for this setting.
name: twenty
# -- Native database or service account username.
username: twenty
# -- Existing Secret containing the external service password.
passwordSecret: ''
# -- Secret key containing the password.
passwordKey: password
# -- Transport encryption and certificate validation settings.
tls:
# -- Enable this capability.
enabled: true
# -- Optional Secret containing an additional PEM CA bundle.
caSecret: ''
# -- Key containing the PEM CA bundle.
caKey: ca.crt
# -- HelmForge PostgreSQL dependency with database-local schema creation privileges.
postgresql:
# -- Initialization actions for an empty PostgreSQL data directory.
initdb:
# -- Native initialization scripts executed in filename order.
scripts:
# -- Grant the application role CREATE on only its dedicated database.
20-twenty-schema.sh: |
#!/bin/bash
set -euo pipefail
export PGPASSWORD="${POSTGRES_PASSWORD}"
psql -v ON_ERROR_STOP=1 --username "${POSTGRES_USER}" --dbname "${APP_DATABASE}" \
--set=app_database="${APP_DATABASE}" --set=app_username="${APP_USERNAME}" <<'SQL'
SELECT format('GRANT CREATE ON DATABASE %I TO %I', :'app_database', :'app_username') \gexec
SQL
# -- Container process privileges and filesystem protection.
securityContext:
# -- Keep image layers read-only; use explicit writable volumes.
readOnlyRootFilesystem: true
# -- Additional explicitly configured Kubernetes volumes.
extraVolumes:
- name: postgres-tmp
# -- Temporary writable storage with an explicit capacity bound.
emptyDir:
# -- Maximum capacity of this temporary volume.
sizeLimit: 1Gi
- name: postgres-socket
# -- Temporary writable storage with an explicit capacity bound.
emptyDir:
# -- Memory-backed storage for the PostgreSQL socket directory.
medium: Memory
# -- Maximum capacity of this temporary volume.
sizeLimit: 16Mi
# -- Additional writable or certificate mount locations.
extraVolumeMounts:
- name: postgres-tmp
# -- Writable path inside the otherwise read-only dependency container.
mountPath: /tmp
- name: postgres-socket
# -- Writable path inside the otherwise read-only dependency container.
mountPath: /var/run/postgresql
# -- Database ingress and egress isolation settings.
networkPolicy:
# -- Enable this capability.
enabled: true
# -- Permitted outbound network traffic.
egress:
# -- Enable this capability.
enabled: true
# -- Allow the database Pod to resolve cluster DNS.
allowDNS: true
# -- Allow PostgreSQL peer egress only for a topology that requires it.
allowSameNamespacePostgreSQL: false
# -- Allow database HTTPS egress only when explicitly required.
allowHTTPS: false
# -- Enable this capability.
enabled: true
# -- Bundled PostgreSQL topology; the supported contract is standalone.
architecture: standalone
# -- Application database and non-superuser role used by native migrations.
auth:
# -- Dedicated database for native core and workspace schemas.
database: twenty
# -- Non-superuser application role with database-local schema creation privileges.
username: twenty
# -- Existing Secret name; keys must match the configured native contract.
existingSecret: ''
# -- Secret key containing the application role password.
existingSecretUserPasswordKey: user-password
# -- Unprivileged NGINX public proxy started after private enrollment succeeds.
proxy:
# -- Pinned official container image settings.
image:
# -- Official container image repository.
repository: docker.io/nginxinc/nginx-unprivileged
# -- Pinned upstream image tag or immutable digest.
tag: 1.30.4-alpine
# -- Kubernetes image pull policy.
pullPolicy: IfNotPresent
# -- Kubernetes CPU and memory allocation.
resources:
# -- Resources reserved by the scheduler.
requests:
# -- Kubernetes CPU quantity.
cpu: 50m
# -- Kubernetes memory quantity.
memory: 32Mi
# -- Maximum resources available to this container.
limits:
# -- Kubernetes CPU quantity.
cpu: 500m
# -- Kubernetes memory quantity.
memory: 128Mi
# -- Maximum public proxy request body size.
bodySize: 20m
# -- Existing or generated retained ENCRYPTION_KEY and SERVER_ID Secret.
identity:
# -- Existing Secret name; keys must match the configured native contract.
existingSecret: ''
# -- Independent fixed-target HMAC helper for actual native-port isolation checks.
admission:
# -- Pinned official container image settings.
image:
# -- Official container image repository.
repository: docker.io/library/node
# -- Pinned upstream image tag or immutable digest.
tag: 24.21.0-alpine
# -- Kubernetes image pull policy.
pullPolicy: IfNotPresent
# -- Kubernetes CPU and memory allocation.
resources:
# -- Resources reserved by the scheduler.
requests:
# -- Kubernetes CPU quantity.
cpu: 25m
# -- Kubernetes memory quantity.
memory: 32Mi
# -- Maximum resources available to this container.
limits:
# -- Kubernetes CPU quantity.
cpu: 200m
# -- Kubernetes memory quantity.
memory: 128Mi
# -- HelmForge authenticated persistent Redis with noeviction for native queues.
redis:
# -- Kubernetes identity for this dependency.
serviceAccount:
# -- Create this Kubernetes resource.
create: true
# -- Container process privileges and filesystem protection.
securityContext:
# -- Keep image layers read-only; use explicit writable volumes.
readOnlyRootFilesystem: true
# -- Linux process capability configuration.
capabilities:
# -- Linux capabilities removed from the container.
drop:
- ALL
# -- System call filtering profile.
seccompProfile:
# -- Use the container runtime default system-call filter.
type: RuntimeDefault
# -- Enable this capability.
enabled: true
# -- Bundled Redis topology; the supported contract is standalone.
architecture: standalone
# -- Redis authentication shared by native cache, PubSub and BullMQ clients.
auth:
# -- Require authentication for the native cache and persistent job queue.
enabled: true
# -- Optional initial password; prefer an existing Secret.
password: ''
# -- Existing Secret name; keys must match the configured native contract.
existingSecret: ''
# -- Secret key containing the native Redis password.
existingSecretPasswordKey: redis-password
# -- Native Redis server configuration for durable queue storage.
config:
# -- Keep noeviction and append-only persistence for BullMQ jobs.
redis: |
maxmemory-policy noeviction
appendonly yes
# -- Standalone dependency workload settings.
standalone:
# -- Retained storage configuration.
persistence:
# -- Enable this capability.
enabled: true
# -- Persistent storage capacity.
size: 5Gi
# -- Kubernetes CPU and memory allocation.
resources:
# -- Resources reserved by the scheduler.
requests:
# -- Kubernetes CPU quantity.
cpu: 100m
# -- Kubernetes memory quantity.
memory: 128Mi
# -- Maximum resources available to this container.
limits:
# -- Kubernetes CPU quantity.
cpu: 500m
# -- Kubernetes memory quantity.
memory: 512Mi
# -- Verified external Redis connection for cache, PubSub and worker queues; disable redis.enabled.
cache:
# -- External service hostname matching the configured certificate.
host: ''
# -- TCP port.
port: 6379
# -- Native database or service account username.
username: ''
# -- Existing Secret containing the external service password.
passwordSecret: ''
# -- Secret key containing the password.
passwordKey: redis-password
# -- Redis logical database index shared by cache, PubSub and job queues.
database: 0
# -- Transport encryption and certificate validation settings.
tls:
# -- Enable this capability.
enabled: true
# -- Optional Secret containing an additional PEM CA bundle.
caSecret: ''
# -- Key containing the PEM CA bundle.
caKey: ca.crt
# -- Native queue worker resources; it shares the server image and local storage.
worker:
# -- Native worker requests and limits; SDK generation runs in this container.
resources:
# -- Resources reserved by the scheduler.
requests:
# -- Kubernetes CPU quantity.
cpu: 250m
# -- Kubernetes memory quantity.
memory: 512Mi
# -- Maximum resources available to this container.
limits:
# -- Kubernetes CPU quantity.
cpu: '2'
# -- Kubernetes memory quantity.
memory: 2Gi
# -- Native local or private S3 storage; the PVC retains enrollment identity in both modes.
storage:
# -- Native storage backend.
driver: local
# -- Configure s3.
s3:
# -- Existing dedicated private bucket.
bucket: ''
# -- S3 signing region.
region: us-east-1
# -- Optional HTTPS S3-compatible endpoint.
endpoint: ''
# -- Existing Secret containing credentials.
existingSecret: ''
# -- Secret key containing the S3 access key ID.
accessKeyIdKey: access-key-id
# -- Secret key containing the S3 secret access key.
secretAccessKeyKey: secret-access-key
# -- Optional Secret containing an additional PEM CA bundle.
caSecret: ''
# -- Secret key containing the PEM CA bundle.
caKey: ca.crt
# -- Authenticated implicit TLS email; port 465 is required by the native SMTP driver.
smtp:
# -- Enable this capability.
enabled: false
# -- Certificate-matching SMTP hostname.
host: ''
# -- Native fixed TCP port.
port: 465
# -- Verified sender address.
from: ''
# -- SMTP authentication identity.
username: ''
# -- Existing Secret containing credentials.
existingSecret: ''
# -- Secret key containing the SMTP password.
passwordKey: password
# -- Configure tls.
tls:
# -- Optional Secret containing an additional PEM CA bundle.
caSecret: ''
# -- Secret key containing the PEM CA bundle.
caKey: ca.crt
# -- Native server Prometheus metrics; the co-located worker has separate health and queue acceptance.
metrics:
# -- Enable this capability.
enabled: false
# -- Native fixed TCP port.
port: 9464
# -- NetworkPolicy peers permitted to scrape the private exporter.
ingressFrom:
- namespaceSelector:
# -- Configure matchLabels.
matchLabels:
kubernetes.io/metadata.name: monitoring
# -- Prometheus Operator configuration for the native server endpoint.
serviceMonitor:
# -- Enable this capability.
enabled: false
# -- Additional operator resource labels.
labels: {}
# -- Prometheus scrape interval.
interval: 30s
# -- Maximum duration of a Prometheus scrape.
scrapeTimeout: 10s
# -- Native target availability alert and additional operator rules.
prometheusRule:
# -- Enable this capability.
enabled: false
# -- Additional operator resource labels.
labels: {}
# -- Additional native Prometheus rules.
additionalRules: []