From 9d613dc19d095cb4f8d32ce2127178466699bc2f Mon Sep 17 00:00:00 2001 From: Lukas Huppertz <45906638+HuppertzL@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:36:10 +0100 Subject: [PATCH] Improve helm chart // Fix linting issues & introduce Redis externalSecret for redis password // Add additional ENVs // Improve migrations (#18157) This pull request enhances the Helm chart for the Twenty application by improving how environment variables and Redis credentials are handled for both server and worker deployments. The main changes include support for injecting additional environment variables, improved Redis password management (including external secrets), and a more robust database migration workflow. **Environment Variable Injection:** - Added support for specifying additional environment variables for both the server and worker deployments via the `additionalEnv` field in `values.yaml`. These variables are automatically injected into the respective pods. [[1]](diffhunk://#diff-b5d958eae48fd1919e5623bcf0144aac7abb323ae8743e6f31367e383c63c296R55) [[2]](diffhunk://#diff-b5d958eae48fd1919e5623bcf0144aac7abb323ae8743e6f31367e383c63c296R109-R110) [[3]](diffhunk://#diff-20bb91909627a12b50b3c165a2a027b663479c0104ed8dbf91d2b9ad8ea8a931R74-R77) [[4]](diffhunk://#diff-20bb91909627a12b50b3c165a2a027b663479c0104ed8dbf91d2b9ad8ea8a931R157-R172) [[5]](diffhunk://#diff-20bb91909627a12b50b3c165a2a027b663479c0104ed8dbf91d2b9ad8ea8a931R225-R229) [[6]](diffhunk://#diff-fb612a3b7a13156aaa607b27d23025e2c6831f111b6a582fd313fad26d2fdb5bR89-R92) **Redis Credential Management:** - Introduced support for using external secrets for Redis passwords by adding `secretName` and `passwordKey` fields under `redis.external` in `values.yaml`, and logic to inject `REDIS_PASSWORD` from a Kubernetes secret if configured. [[1]](diffhunk://#diff-b5d958eae48fd1919e5623bcf0144aac7abb323ae8743e6f31367e383c63c296R180-R182) [[2]](diffhunk://#diff-5c4fa358b10abd7581188995feb9b4d6be0bc4f06a95bf27bb31b5595d6693d8R92-R100) [[3]](diffhunk://#diff-20bb91909627a12b50b3c165a2a027b663479c0104ed8dbf91d2b9ad8ea8a931R157-R172) [[4]](diffhunk://#diff-20bb91909627a12b50b3c165a2a027b663479c0104ed8dbf91d2b9ad8ea8a931R196-R205) [[5]](diffhunk://#diff-fb612a3b7a13156aaa607b27d23025e2c6831f111b6a582fd313fad26d2fdb5bR70-R79) - Updated the logic for constructing the `REDIS_URL` to include authentication information if a password is set or an external secret is used. **Database Migration Workflow:** - Improved the startup command for the server deployment to optionally skip database migrations (using `DISABLE_DB_MIGRATIONS`), check for an existing schema before running migrations, and ensure setup scripts are only run on empty databases. These changes make the chart more flexible and secure, especially for production deployments requiring externalized secrets and custom environment configurations. --------- Co-authored-by: Charles Bochet --- .../helm/twenty/templates/_helpers.tpl | 14 +++++ .../templates/deployment-redis-internal.yaml | 16 ++--- .../twenty/templates/deployment-server.yaml | 60 ++++++++----------- .../twenty/templates/deployment-worker.yaml | 13 ++++ .../twenty/templates/pvc-redis-internal.yaml | 20 +++++++ .../twenty-docker/helm/twenty/values.yaml | 13 ++++ 6 files changed, 92 insertions(+), 44 deletions(-) create mode 100644 packages/twenty-docker/helm/twenty/templates/pvc-redis-internal.yaml diff --git a/packages/twenty-docker/helm/twenty/templates/_helpers.tpl b/packages/twenty-docker/helm/twenty/templates/_helpers.tpl index 661fb67c252..f9640baf2e0 100644 --- a/packages/twenty-docker/helm/twenty/templates/_helpers.tpl +++ b/packages/twenty-docker/helm/twenty/templates/_helpers.tpl @@ -89,6 +89,15 @@ password {{- end -}} {{- end -}} +{{/* Check if using external secret for redis password */}} +{{- define "twenty.redis.useExternalSecret" -}} +{{- if and (not .Values.redisInternal.enabled) .Values.redis.external.secretName .Values.redis.external.passwordKey -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + {{/* Compose Redis URL */}} {{- define "twenty.redisUrl" -}} {{- if .Values.server.env.REDIS_URL -}} @@ -99,9 +108,14 @@ password {{- else -}} {{- $host := .Values.redis.external.host | default "redis" -}} {{- $port := .Values.redis.external.port | default 6379 -}} +{{- if or (eq (include "twenty.redis.useExternalSecret" .) "true") (.Values.redis.external.password) -}} +{{- $auth := ":$(REDIS_PASSWORD)@" -}} +{{- printf "redis://%s%s:%v" $auth $host $port -}} +{{- else -}} {{- printf "redis://%s:%v" $host $port -}} {{- end -}} {{- end -}} +{{- end -}} {{/* Compose Server URL from override, ingress, or service */}} {{- define "twenty.serverUrl" -}} diff --git a/packages/twenty-docker/helm/twenty/templates/deployment-redis-internal.yaml b/packages/twenty-docker/helm/twenty/templates/deployment-redis-internal.yaml index b0a3172813a..8809bff337c 100644 --- a/packages/twenty-docker/helm/twenty/templates/deployment-redis-internal.yaml +++ b/packages/twenty-docker/helm/twenty/templates/deployment-redis-internal.yaml @@ -46,12 +46,12 @@ spec: volumeMounts: - name: redis-data mountPath: /data - volumes: - - name: redis-data - {{- if .Values.redisInternal.persistence.enabled }} - persistentVolumeClaim: - claimName: {{ .Values.redisInternal.persistence.existingClaim | default (printf "%s-redis" (include "twenty.fullname" .)) }} - {{- else }} - emptyDir: {} - {{- end }} + volumes: + - name: redis-data + {{- if .Values.redisInternal.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.redisInternal.persistence.existingClaim | default (printf "%s-redis" (include "twenty.fullname" .)) }} + {{- else }} + emptyDir: {} + {{- end }} {{- end }} diff --git a/packages/twenty-docker/helm/twenty/templates/deployment-server.yaml b/packages/twenty-docker/helm/twenty/templates/deployment-server.yaml index d2b4f154710..e262422099f 100644 --- a/packages/twenty-docker/helm/twenty/templates/deployment-server.yaml +++ b/packages/twenty-docker/helm/twenty/templates/deployment-server.yaml @@ -83,16 +83,16 @@ spec: psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v db="${DBNAME}" -Atc "SELECT 1 FROM pg_database WHERE datname = :'db'" | grep -q 1 || \ psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v db="${DBNAME}" -c 'CREATE DATABASE :"db";' echo "Creating app user ${APP_USER} if it doesn't exist..." - psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v app_user="${APP_USER}" -v app_password="${APP_PASSWORD}" <<'EOSQL' - DO - $do$ - BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'app_user') THEN - EXECUTE format('CREATE USER %I WITH PASSWORD %L', :'app_user', :'app_password'); - END IF; - END - $do$; - EOSQL + psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v app_user="${APP_USER}" -v app_password="${APP_PASSWORD}" <<'EOSQL' + DO + $do$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'app_user') THEN + EXECUTE format('CREATE USER %I WITH PASSWORD %L', :'app_user', :'app_password'); + END IF; + END + $do$; + EOSQL echo "Creating core schema and granting permissions..." psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'CREATE SCHEMA IF NOT EXISTS core' psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v db="${DBNAME}" -v app_user="${APP_USER}" -c 'GRANT ALL PRIVILEGES ON DATABASE :"db" TO :"app_user";' @@ -106,31 +106,6 @@ spec: psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO :"app_user";' echo "Database ${DBNAME} is ready." {{- end }} - - name: run-migrations - {{- $img := include "twenty.server.image" . }} - image: {{ include "twenty.image.repository" $img }}:{{ include "twenty.image.tag" $img }} - imagePullPolicy: {{ include "twenty.image.pullPolicy" $img }} - command: - - sh - - -c - - >- - npx -y typeorm migration:run -d dist/database/typeorm/core/core.datasource - env: - {{- if eq (include "twenty.db.useExternalSecret" .) "true" }} - - name: DB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "twenty.dbPassword.secretName" . }} - key: {{ include "twenty.dbPassword.secretKey" . }} - - name: PG_DATABASE_URL - value: {{ include "twenty.dbUrl.template" . | quote }} - {{- else }} - - name: PG_DATABASE_URL - valueFrom: - secretKeyRef: - name: {{ include "twenty.dbUrl.secretName" . }} - key: url - {{- end }} containers: - name: server {{- $img := include "twenty.server.image" . }} @@ -154,6 +129,16 @@ spec: name: {{ include "twenty.dbUrl.secretName" . }} key: url {{- end }} + {{- if eq (include "twenty.redis.useExternalSecret" .) "true" }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.redis.external.secretName }} + key: {{ .Values.redis.external.passwordKey }} + {{- else if .Values.redis.external.password }} + - name: REDIS_PASSWORD + value: {{ .Values.redis.external.password | quote }} + {{- end }} - name: REDIS_URL value: {{ include "twenty.redisUrl" . | quote }} - name: SIGN_IN_PREFILLED @@ -171,7 +156,10 @@ spec: key: accessToken {{- $storageEnv := (include "twenty.storageEnv" .) }} {{- if $storageEnv }} - {{ $storageEnv | nindent 12 }} + {{- $storageEnv | nindent 12 }} + {{- end }} + {{- with .Values.server.extraEnv }} + {{- toYaml . | nindent 12 }} {{- end }} ports: - name: http-tcp diff --git a/packages/twenty-docker/helm/twenty/templates/deployment-worker.yaml b/packages/twenty-docker/helm/twenty/templates/deployment-worker.yaml index dc47d13a08b..d2e506d053a 100644 --- a/packages/twenty-docker/helm/twenty/templates/deployment-worker.yaml +++ b/packages/twenty-docker/helm/twenty/templates/deployment-worker.yaml @@ -67,6 +67,16 @@ spec: name: {{ include "twenty.dbUrl.secretName" . }} key: url {{- end }} + {{- if eq (include "twenty.redis.useExternalSecret" .) "true" }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.redis.external.secretName }} + key: {{ .Values.redis.external.passwordKey }} + {{- else if .Values.redis.external.password }} + - name: REDIS_PASSWORD + value: {{ .Values.redis.external.password | quote }} + {{- end }} - name: REDIS_URL value: {{ include "twenty.redisUrl" . | quote }} - name: STORAGE_TYPE @@ -76,6 +86,9 @@ spec: secretKeyRef: name: {{ include "twenty.secret.tokens.name" . }} key: accessToken + {{- with .Values.worker.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} {{- $storageEnv := (include "twenty.storageEnv" .) }} {{- if $storageEnv }} {{ $storageEnv | nindent 12 }} diff --git a/packages/twenty-docker/helm/twenty/templates/pvc-redis-internal.yaml b/packages/twenty-docker/helm/twenty/templates/pvc-redis-internal.yaml new file mode 100644 index 00000000000..730d556c031 --- /dev/null +++ b/packages/twenty-docker/helm/twenty/templates/pvc-redis-internal.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.redisInternal.enabled .Values.redisInternal.persistence.enabled (not .Values.redisInternal.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "twenty.fullname" . }}-redis + namespace: {{ include "twenty.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "twenty.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: redis +spec: + accessModes: +{{ toYaml .Values.redisInternal.persistence.accessModes | nindent 4 }} + resources: + requests: + storage: {{ .Values.redisInternal.persistence.size }} + {{- if .Values.redisInternal.persistence.storageClass }} + storageClassName: {{ .Values.redisInternal.persistence.storageClass }} + {{- end }} +{{- end }} diff --git a/packages/twenty-docker/helm/twenty/values.yaml b/packages/twenty-docker/helm/twenty/values.yaml index c9e7623997c..088c9e5a9c5 100644 --- a/packages/twenty-docker/helm/twenty/values.yaml +++ b/packages/twenty-docker/helm/twenty/values.yaml @@ -52,6 +52,14 @@ server: SIGN_IN_PREFILLED: "false" ACCESS_TOKEN_EXPIRES_IN: "7d" LOGIN_TOKEN_EXPIRES_IN: "1h" + extraEnv: [] + # - name: EMAIL_DRIVER + # value: smtp + # - name: SMTP_PASSWORD + # valueFrom: + # secretKeyRef: + # name: smtp-creds + # key: password service: type: ClusterIP @@ -105,6 +113,8 @@ worker: cpu: 1000m memory: 2048Mi + extraEnv: [] + # PostgreSQL db: enabled: true @@ -174,3 +184,6 @@ redis: external: host: "" port: 6379 + password: "" + secretName: "" + passwordKey: ""