Environment-Specific Configuration for Custom Flows
Overview
When customers add custom Node-RED flows that call external APIs with custom TLS certificates, they often hardcode API endpoints and cert paths across multiple nodes. This makes it painful to maintain separate QA, Staging, and Production environments since every value must be manually edited per environment.
This guide explains how to externalize these values so that flows.json remains identical across all environments, with configuration injected through Helm.
Externalizing API Endpoints and Custom Variables
How siteEnvVars Works
The Helm chart has two sections for environment variables:
-
extraEnvVars— Default variables shipped with the chart (e.g.,CONVERSATION_MANAGER_INTERNAL_URL,TENANT_ID). Maintained by Expertflow. -
siteEnvVars— Customer-specific variables. Empty by default. Customers add their own entries here.
Both render into the container's environment. Customers can add completely new variables that don't exist in the original chart — they are not limited to overriding existing ones.
Defining Variables
Add custom variables in the Helm values override file:
# customer-values-qa.yaml
conversation-studio:
siteEnvVars:
- name: EXT_CUSTOM_API_URL
value: "https://custom-endpoint.com/api"
For production, maintain a separate file with production values:
# customer-values-prod.yaml
conversation-studio:
siteEnvVars:
- name: EXT_CUSTOM_API_URL
value: "https://custom-endpoint.com/api"
Deploy with the appropriate file:
Using Variables in Node-RED
In Function Nodes — use env.get():
// Instead of hardcoding:
// const crmUrl = "https://crm.customer.com/api/v2";
const crmUrl = env.get("EXT_CUSTOM_API_URL");
const apiKey = env.get("EXT_CUSTOM_API_KEY");
const response = await fetch(crmUrl + "/contacts/" + msg.payload.customerId, {
headers: { "Authorization": "Bearer " + apiKey }
});
In HTTP Request Node URL fields — use ${VAR_NAME} syntax directly:
${EXT_CUSTOM_API_URL}/contacts
Upgrade Compatibility
Since siteEnvVars live in the customer's override file (not in the chart), they persist across chart upgrades automatically. The customer upgrades to a new chart version and their custom variables carry forward — no changes needed. This follows the same pattern used by other components (e.g., bot-framework uses siteEnvVars the same way).
Naming Convention
Prefix all custom variables with EXT_ to distinguish them from built-in Conversation Studio variables (e.g., EXT_CUSTOM_API_URL, EXT_CRM_API_KEY, EXT_CLIENT_CERT_PATH).
Externalizing TLS Certificates
Certificates are files, not strings, so they require a different mechanism: Kubernetes secrets mounted as volumes.
Step 1: Create a Kubernetes Secret
Each environment has its own secret with different cert content but the same file names:
# QA
kubectl create secret generic customer-ext-certs \
--from-file=client.pem=./qa-client.pem \
--from-file=client-key.pem=./qa-client-key.pem \
--from-file=ca.pem=./qa-ca.pem \
-n <namespace>
# Prod (same secret name, same file names, different content)
kubectl create secret generic customer-ext-certs \
--from-file=client.pem=./prod-client.pem \
--from-file=client-key.pem=./prod-client-key.pem \
--from-file=ca.pem=./prod-ca.pem \
-n <namespace>
Step 2: Mount the Secret
There are two ways to mount custom volumes, depending on chart support.
Option A: Using siteVolumes / siteVolumeMounts (Recommended)
Like siteEnvVars, the chart can provide siteVolumes and siteVolumeMounts that render separately from extraVolumes and extraVolumeMounts. The customer only specifies their custom volumes — no need to repeat the chart defaults:
conversation-studio:
siteVolumes:
- name: customer-ext-certs
secret:
secretName: customer-ext-certs
siteVolumeMounts:
- name: customer-ext-certs
mountPath: /certs/custom
The chart template appends these alongside the defaults (redis-crt, mongo-mongodb-ca, etc.), so nothing is lost. This is upgrade-safe and consistent with the siteEnvVars pattern.
Note: If the chart does not yet support siteVolumes/siteVolumeMounts, this requires a chart enhancement. See Option B for the current workaround.
Option B: Using extraVolumes / extraVolumeMounts (Workaround)
Helm performs a full replacement on arrays. If the customer overrides extraVolumes, it replaces the entire list. The customer must include the original defaults alongside their custom entries:
conversation-studio:
extraVolumes:
# Defaults (must be repeated)
- name: redis-crt
secret:
secretName: redis-crt
- name: mongo-mongodb-ca
secret:
secretName: mongo-mongodb-ca
# Custom
- name: customer-ext-certs
secret:
secretName: customer-ext-certs
extraVolumeMounts:
# Defaults (must be repeated)
- name: redis-crt
mountPath: /redis
- name: mongo-mongodb-ca
mountPath: /mongo
- name: conversation-studio-flow-vol
mountPath: /flows
# Custom
- name: customer-ext-certs
mountPath: /certs/custom
Caution: If a new chart version changes the default extraVolumes, the customer must update their override file to match. The upgrade guide should call this out.
Step 3: Reference Certificates in Node-RED
The mount path (/certs/custom/) and file names are fixed, so the paths are the same across all environments. Only the file content differs per namespace.
Option A: TLS Config in HTTP Request Node (Recommended)
-
Drag an HTTP Request node onto the canvas and double-click to open properties.
-
Check "Enable secure (SSL/TLS) connection".
-
Click the pencil icon next to TLS Configuration to create a new TLS config node.
-
Fill in the paths:
-
Certificate:
/certs/custom/client.pem -
Private Key:
/certs/custom/client-key.pem -
CA Certificate:
/certs/custom/ca.pem
-
-
Name it (e.g., "Customer External APIs TLS") and click Add.
-
Set the URL field to
${EXT_CRM_API_URL}/contacts.
Other HTTP Request nodes can select the same TLS config from the dropdown — configure once, reuse everywhere. This also works with the Custom HTTP Request node that ships with Conversation Studio.
Option B: Loading Certificates in Function Nodes
For scenarios needing full control over the HTTP client:
const fs = require("fs");
const https = require("https");
const agent = new https.Agent({
cert: fs.readFileSync("/certs/custom/client.pem"),
key: fs.readFileSync("/certs/custom/client-key.pem"),
ca: fs.readFileSync("/certs/custom/ca.pem"),
});
const url = env.get("EXT_PAYMENT_API_URL");
const res = await fetch(url + "/charge", {
method: "POST",
agent: agent,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(msg.payload),
});
msg.payload = await res.json();
return msg;
Summary
|
Item |
Per Environment? |
Where It Lives |
|---|---|---|
|
API URLs / keys |
Different per env |
|
|
Certificate file content |
Different per env |
Kubernetes secret (per namespace) |
|
Certificate mount path |
Same across envs |
|
|
TLS config node / Function code |
Same across envs |
In |
flows.json is identical across all environments. The only environment-specific inputs are the Helm values file and the Kubernetes secret.