diff --git a/charts/plane-enterprise/Chart.yaml b/charts/plane-enterprise/Chart.yaml
index 32f83301..e1eb826e 100644
--- a/charts/plane-enterprise/Chart.yaml
+++ b/charts/plane-enterprise/Chart.yaml
@@ -5,7 +5,7 @@ description: Meet Plane. An Enterprise software development tool to manage issue
type: application
-version: 3.0.0
+version: 3.1.0
appVersion: "3.0.0"
home: https://plane.so/
diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md
index 1fa17fe0..8e9c9572 100644
--- a/charts/plane-enterprise/README.md
+++ b/charts/plane-enterprise/README.md
@@ -455,6 +455,7 @@ securityContext:
| env.live_sentry_environment | | | (optional) Live service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry environment name (as configured in Sentry) for this integration. |
| env.live_sentry_traces_sample_rate | | | (optional) Live service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry trace sample rate (as configured in Sentry) for this integration. |
| env.live_server_secret_key | htbqvBJAgpm9bzvf3r4urJer0ENReatceh | | Live Server Secret Key |
+| env.export_queue_name | plane-exports | | RabbitMQ queue name for background PDF/DOCX export jobs consumed by the `live-exporter` service. |
| env.external_iframely_url | "" | | External Iframely service URL. If provided, the local Iframely deployment will be skipped and the live service will use this external URL |
| services.live.assign_cluster_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service |
| services.live.nodeSelector | {} | | This key allows you to set the node selector for the deployment of `live`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. |
@@ -463,6 +464,21 @@ securityContext:
| services.live.labels | {} | | Custom labels to add to the live deployment |
| services.live.annotations | {} | | Custom annotations to add to the live deployment |
+### Live Exporter Deployment
+
+| Setting | Default | Required | Description |
+| ----------------------------------------- | :------------------------: | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| services.live_exporter.enabled | true | | Enable or disable the background PDF/DOCX export worker. Reuses the `live` image but boots in `exporter` mode (pure queue consumer, no HTTP port). |
+| services.live_exporter.replicas | 1 | Yes | Number of exporter pods. PDF render footprint is 500MB–2GB per job; scale horizontally rather than increasing concurrency per pod. |
+| services.live_exporter.memoryLimit | 2000Mi | | Memory limit for the exporter pod. Set higher if rendering large documents. |
+| services.live_exporter.cpuLimit | 1000m | | CPU limit for the exporter pod. |
+| services.live_exporter.memoryRequest | 50Mi | | Memory request for the exporter pod. |
+| services.live_exporter.cpuRequest | 50m | | CPU request for the exporter pod. |
+| services.live_exporter.image | makeplane/live-commercial | | Docker image for the exporter. Must match the `live` service image. |
+| services.live_exporter.nodeSelector | {} | | Node selector for the exporter pod. |
+| services.live_exporter.tolerations | [] | | Tolerations for the exporter pod. |
+| services.live_exporter.affinity | {} | | Affinity rules for the exporter pod. |
+
### Monitor Deployment
| Setting | Default | Required | Description |
@@ -874,6 +890,8 @@ To configure the external secrets for your application, you need to define speci
| | `DATABASE_URL` | Yes | PostgreSQL connection URL | **k8s service example**: `postgresql://plane:plane@plane-pgdb.plane-ns.svc.cluster.local:5432/plane`
**external service example**: `postgresql://username:password@your-db-host:5432/plane` |
| | `AMQP_URL` | Yes | RabbitMQ connection URL | **k8s service example**: `amqp://plane:plane@plane-rabbitmq.plane-ns.svc.cluster.local:5672/`
**external service example**: `amqp://username:password@your-rabbitmq-host:5672/` |
| live_env_existingSecret | `REDIS_URL` | Yes | Redis URL | `redis://plane-redis.plane-ns.svc.cluster.local:6379/` |
+| | `AMQP_URL` | Yes | RabbitMQ connection URL | **k8s service example**: `amqp://plane:plane@plane-rabbitmq.plane-ns.svc.cluster.local:5672/`
**external service example**: `amqp://username:password@your-rabbitmq-host:5672/` |
+| | `LIVE_SERVER_SECRET_KEY` | Yes | Live server secret key | `htbqvBJAgpm9bzvf3r4urJer0ENReatceh` |
| silo_env_existingSecret | `SILO_HMAC_SECRET_KEY` | Yes | Silo HMAC secret Key | `` |
| | `REDIS_URL` | Yes | Redis URL | `redis://plane-redis.plane-ns.svc.cluster.local:6379/` |
| | `DATABASE_URL` | Yes | PostgreSQL connection URL | **k8s service example**: `postgresql://plane:plane@plane-pgdb.plane-ns.svc.cluster.local:5432/plane`
**external service example**: `postgresql://username:password@your-db-host:5432/plane` |
diff --git a/charts/plane-enterprise/questions.yml b/charts/plane-enterprise/questions.yml
index 12241b87..209d627d 100644
--- a/charts/plane-enterprise/questions.yml
+++ b/charts/plane-enterprise/questions.yml
@@ -267,6 +267,52 @@ questions:
label: "Live Server Secret Key"
type: string
default: "htbqvBJAgpm9bzvf3r4urJer0ENReatceh"
+ - variable: env.export_queue_name
+ label: "Export Queue Name"
+ description: "RabbitMQ queue name for background PDF/DOCX export jobs consumed by the live-exporter service."
+ type: string
+ default: "plane-exports"
+ - variable: env.export_download_base_url
+ label: "Export Download Base URL"
+ description: "Base URL for export download redirects. Defaults to licenseDomain with protocol inferred from SSL settings."
+ type: string
+ default: ""
+
+- variable: services.live_exporter.enabled
+ label: "Install Live Exporter Service"
+ type: boolean
+ default: true
+ group: "Live Exporter Setup"
+ show_subquestion_if: true
+ subquestions:
+ - variable: services.live_exporter.replicas
+ label: "Default Replica Count"
+ type: int
+ default: 1
+ - variable: services.live_exporter.pullPolicy
+ label: "Live Exporter Pull Policy"
+ type: enum
+ options:
+ - "Always"
+ - "IfNotPresent"
+ - "Never"
+ default: "Always"
+ - variable: services.live_exporter.memoryLimit
+ label: "Memory Limit"
+ type: string
+ default: 2000Mi
+ - variable: services.live_exporter.cpuLimit
+ label: "CPU Limit"
+ type: string
+ default: 1000m
+ - variable: services.live_exporter.memoryRequest
+ label: "Memory Request"
+ type: string
+ default: 50Mi
+ - variable: services.live_exporter.cpuRequest
+ label: "CPU Request"
+ type: string
+ default: 50m
- variable: services.silo.enabled
label: "Install Silo Service"
diff --git a/charts/plane-enterprise/templates/config-secrets/live-env.yaml b/charts/plane-enterprise/templates/config-secrets/live-env.yaml
index 16855c46..5f249ce6 100644
--- a/charts/plane-enterprise/templates/config-secrets/live-env.yaml
+++ b/charts/plane-enterprise/templates/config-secrets/live-env.yaml
@@ -14,6 +14,13 @@ stringData:
{{- else }}
REDIS_URL: {{ .Values.env.remote_redis_url | default "" | quote }}
{{- end }}
+ {{- if .Values.services.rabbitmq.local_setup }}
+ AMQP_URL: "amqp://{{ .Values.services.rabbitmq.default_user }}:{{ .Values.services.rabbitmq.default_password }}@{{ .Release.Name }}-rabbitmq.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}/"
+ {{- else if .Values.services.rabbitmq.external_rabbitmq_url }}
+ AMQP_URL: {{ .Values.services.rabbitmq.external_rabbitmq_url | quote }}
+ {{- else }}
+ AMQP_URL: ""
+ {{- end }}
{{- end }}
---
apiVersion: v1
@@ -41,4 +48,13 @@ data:
{{- else }}
IFRAMELY_URL: http://{{ .Release.Name }}-iframely.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:8061/
{{- end }}
+
+ {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }}
+ EXPORT_DOWNLOAD_BASE_URL: {{ .Values.env.export_download_base_url | default (printf "https://%s" .Values.license.licenseDomain) | quote }}
+ {{- else }}
+ EXPORT_DOWNLOAD_BASE_URL: {{ .Values.env.export_download_base_url | default (printf "http://%s" .Values.license.licenseDomain) | quote }}
+ {{- end }}
+
+ EXPORT_QUEUE_NAME: {{ .Values.env.export_queue_name | default "plane-exports" | quote }}
+
---
diff --git a/charts/plane-enterprise/templates/workloads/live-exporter.deployment.yaml b/charts/plane-enterprise/templates/workloads/live-exporter.deployment.yaml
new file mode 100644
index 00000000..eb781a73
--- /dev/null
+++ b/charts/plane-enterprise/templates/workloads/live-exporter.deployment.yaml
@@ -0,0 +1,54 @@
+
+{{- if .Values.services.live_exporter.enabled }}
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-live-exporter-wl
+ {{- include "plane.labelsAndAnnotations" (dict "context" $ "values" .Values.services.live_exporter) }}
+spec:
+ replicas: {{ .Values.services.live_exporter.replicas | default 1 }}
+ selector:
+ matchLabels:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-live-exporter
+ template:
+ metadata:
+ namespace: {{ .Release.Namespace }}
+ labels:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-live-exporter
+ {{- include "plane.commonLabels" $ | nindent 8 }}
+ annotations:
+ timestamp: {{ now | quote }}
+ spec:
+ {{- include "plane.podScheduling" .Values.services.live_exporter }}
+ {{- include "plane.podSecurityContext" . }}
+ containers:
+ - name: {{ .Release.Name }}-live-exporter
+ {{- include "plane.containerSecurityContext" . }}
+ imagePullPolicy: {{ .Values.services.live_exporter.pullPolicy | default "Always" }}
+ image: {{ .Values.services.live_exporter.image | default "makeplane/live-commercial" }}:{{ .Values.planeVersion }}
+ stdin: true
+ tty: true
+ resources:
+ requests:
+ memory: {{ .Values.services.live_exporter.memoryRequest | default "50Mi" | quote }}
+ cpu: {{ .Values.services.live_exporter.cpuRequest | default "50m" | quote }}
+ limits:
+ memory: {{ .Values.services.live_exporter.memoryLimit | default "2000Mi" | quote }}
+ cpu: {{ .Values.services.live_exporter.cpuLimit | default "1000m" | quote }}
+ envFrom:
+ - configMapRef:
+ name: {{ .Release.Name }}-live-vars
+ optional: false
+ - secretRef:
+ name: {{ if not (empty .Values.external_secrets.live_env_existingSecret) }}{{ .Values.external_secrets.live_env_existingSecret }}{{ else }}{{ .Release.Name }}-live-secrets{{ end }}
+ optional: false
+ env:
+ - name: LIVE_MODE
+ value: "exporter"
+ {{- if .Values.extraEnv }}
+ {{- toYaml .Values.extraEnv | nindent 10 }}
+ {{- end }}
+ serviceAccount: {{ .Release.Name }}-srv-account
+ serviceAccountName: {{ .Release.Name }}-srv-account
+{{- end }}
diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml
index 8394e9af..999c8416 100644
--- a/charts/plane-enterprise/values.yaml
+++ b/charts/plane-enterprise/values.yaml
@@ -273,6 +273,22 @@ services:
labels: {}
annotations: {}
+ live_exporter:
+ enabled: true
+ replicas: 1
+ # PDF render footprint is 500MB-2GB; default limits reflect that.
+ memoryLimit: 2000Mi
+ cpuLimit: 1000m
+ memoryRequest: 50Mi
+ cpuRequest: 50m
+ image: makeplane/live-commercial
+ pullPolicy: Always
+ nodeSelector: {}
+ tolerations: []
+ affinity: {}
+ labels: {}
+ annotations: {}
+
api:
replicas: 1
memoryLimit: 1000Mi
@@ -614,6 +630,11 @@ env:
live_sentry_environment: ""
live_sentry_traces_sample_rate: ""
live_server_secret_key: "htbqvBJAgpm9bzvf3r4urJer0ENReatceh"
+
+ # Background PDF/DOCX export worker queue.
+ export_queue_name: "plane-exports"
+ # Base URL for export download redirects. Defaults to licenseDomain with protocol from SSL settings.
+ export_download_base_url: ""
external_iframely_url: ""
silo_envs: