Configure Patient Journey Intelligence
This page is the shared configuration reference for the Patient Journey Intelligence environment guides. It covers managed cluster ingress, shared storage, Databricks, HTTPS and TLS, the installer option reference, environment variables, and managed AWS services, with configuration examples for each.
Choose your execution context
Examples using sudo are for the K3s host. On EKS, GKE, AKS, and OpenShift, run the release scripts as the authenticated administration user, without sudo, and include the matching --eks, --gke, --aks, or --openshift flag. Using sudo there may select root's kubeconfig instead of your cluster credentials.
For every update example, retain the deployed --host, platform mode, namespace, release name, and --values files. Add --tls and your explicit issuer selection if the deployment uses HTTPS. The updater does not automatically preserve these options. See Upgrading.
Managed cluster ingress
The scripts expect ingress class nginx on EKS, GKE, and AKS. Community ingress-nginx maintenance ended in March 2026; see the Kubernetes retirement statement. The commands below configure that controller. Confirm your organization's supported ingress setup before a new production deployment.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
kubectl create namespace ingress-nginx --dry-run=client -o yaml | kubectl apply -f -
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
-n ingress-nginx \
--set controller.service.type=LoadBalancer \
--set controller.ingressClass=nginx \
--set controller.ingressClassResource.name=nginx \
--set controller.watchIngressWithoutClass=false
Get the ingress controller external address (an IP address or hostname):
kubectl get svc -n ingress-nginx ingress-nginx-controller -w
Shared document and pipeline artifact storage
Patient Journey Intelligence stores uploaded documents, pipeline working files, generated exports,
and related document artifacts on global.sharedPvcs.docStorage. All
long-running services and dynamically launched pipeline pods resolve the same
claim name from the chart ConfigMap. Other state, such as PostgreSQL,
Elasticsearch, model caches, and Airflow logs, uses separate persistence.
The chart supports three deployment patterns:
- Dynamic PVC (default): leave
existingClaimempty andlocal.enabledfalse. SetstorageClass,size, andaccessModesin an environment values file. An emptystorageClassuses the cluster default. - Ops-managed PVC: set
create: falseandexistingClaimto a claim in the release namespace. Use an RWX-capable backend for multi-node deployments. - Node-local PV: set
local.enabled: true,local.path, andlocal.nodeName. This provides a predictable host directory and is intended for single-node K3s/on-prem installations. The generated PV uses node affinity and defaults to theRetainreclaim policy.
Environment values can be supplied to both release scripts with the repeatable
--values/-f option. Use the same file on every update; the updater's
explicit Helm settings preserve application secrets but do not remember an
omitted environment values file.
Single-node host directory
Get the Kubernetes hostname label and prepare the directory before installing:
NODE_NAME=$(kubectl get nodes -o jsonpath='{.items[0].metadata.labels.kubernetes\.io/hostname}')
sudo install -d -o root -g 0 -m 0770 /var/lib/pj-app/artifacts
Copy helm-charts/ops-config/storage/local-host.values.yaml beside the release
artifacts, replace REPLACE_WITH_KUBERNETES_NODE_NAME, and install or update:
sudo ./pj-app-installer.sh \
--host pjapp.example.com \
--namespace dev \
--values local-host.values.yaml
sudo ./pj-app-updater.sh --host pjapp.example.com \
--namespace dev \
--values local-host.values.yaml
Do not reuse the same host directory for two releases. Local PV data stays on
that node and workloads using the claim are scheduled there by PV node
affinity. Back up /var/lib/pj-app/artifacts with the host's normal filesystem
backup process.
Existing RWX claim
For EFS, Azure Files, Filestore, NFS, or another Ops-managed RWX backend, copy
helm-charts/ops-config/storage/existing-rwx.values.yaml, set the claim name,
and pass it to both installer and updater. The claim must be in the Patient Journey Intelligence
namespace.
Changing storageClass, access modes, volumeName, or storage strategy on an
already bound PVC is generally not an in-place upgrade. Provision a new claim,
copy the data, update existingClaim (or the local-PV configuration), and only
then retire the old claim. Keep the old volume until the migrated deployment is
confirmed healthy.
Permissions
Patient Journey Intelligence uses a shared filesystem group instead of world-writable permissions.
The default is group 0 with fsGroupChangePolicy: OnRootMismatch, matching
the bundled Airflow deployment and dynamically launched pipeline pods. If an
environment requires another group, override both
global.sharedPvcs.docStorage.permissions.fsGroup and
airflow.securityContexts.pod.fsGroup. The CSI driver or filesystem export
must permit ownership/group changes; for managed NFS/EFS services, configure
the export or access point accordingly.
Pipeline caches, shared dependencies, and license file
Pipeline caching is enabled by default (global.sharedPvcs.pipelineCache.enabled: true). Dynamic pipeline pods mount the claim at four independently configurable paths. Set enabled: false to disable the cache PVC and its cache initialization. Each cache uses
its own subPath on the claim; an init container creates those directories
before the pipeline starts. The defaults are johnsnowlabs, jsl_models,
cache_pretrained, and jsl_pretrained. Override storageClass, size,
accessModes, or any entry under caches in the environment values file.
EKS/OpenShift release scripts select the configured EFS/RWX StorageClass for
this claim automatically when it is enabled. On self-managed clusters without
--nfs, the scripts select ReadWriteOnce for the cache. Use --nfs for
multi-node K3s cache sharing; it provisions NFS-backed RWX storage for the
pipeline cache only. Configure other shared volumes separately.
Shared PySpark JARs/Wheels are deliberately disabled by default so an empty
claim cannot hide dependencies baked into /opt/spark/jars. To use an
Ops-populated claim, enable sharedDependencies; its volume name and mount
path are configurable and the pipeline mount is always read-only:
global:
sharedPvcs:
pipelineCache:
enabled: true
size: 50Gi
caches:
johnsnowlabs:
mountPath: /nobody/.johnsnowlabs
subPath: johnsnowlabs
jslModels:
mountPath: /opt/model_cache
subPath: jsl_models
cachePretrained:
mountPath: /root/cache_pretrained
subPath: cache_pretrained
jslPretrained:
mountPath: /nobody/.cache/jsl_pretrained
subPath: jsl_pretrained
sharedDependencies:
enabled: true
create: false
existingClaim: pj-app-shared-jars
volumeName: pyspark-jars
mountPath: /opt/spark/jars
When the chart-managed license Secret is enabled, dynamic pipeline pods also
mount its license.json key read-only at
global.secrets.license.pipelineMount.path. Database-derived license
environment variables are still injected, so this file mount is additive.
Databricks
When exporting settings before using sudo, pass sudo -E so the release script receives those variables. Your sudo policy must allow environment preservation.
Information extraction timeout
The Data Curation Service (DCS) waits up to two hours by default for a Databricks information extraction run. Configure a different timeout in milliseconds with Helm:
config:
databricks:
ieTimeoutMs: 10800000 # three hours
For one-off installs or updates, export DATABRICKS_IE_TIMEOUT_MS; both
pj-app-installer.sh and pj-app-updater.sh forward it to Helm:
export DATABRICKS_IE_TIMEOUT_MS=10800000
sudo -E ./pj-app-installer.sh --namespace dev
# or
sudo -E ./pj-app-updater.sh --host pjapp.example.com --namespace dev
If the variable is unset, the chart default of 7200000 milliseconds is used.
ECR-hosted images
Export only the ECR password before running either release script. The scripts derive the registry username, registry, and Patient Journey Intelligence image version from the artifact filename. They configure the standard Databricks images and the DCS Custom NLP image automatically:
export ECR_PASSWORD="$(aws ecr get-login-password --region us-east-1)"
sudo -E ./pj-app-installer.sh --namespace dev
# or
sudo -E ./pj-app-updater.sh --host pjapp.example.com --namespace dev
The default custom NLP tag is
<pj-app-version>_custom_nlp_databricks. Set
ECR_CUSTOM_NLP_DATABRICKS_IMAGE only when using a different image reference.
HTTPS and TLS (Let's Encrypt)
Existing cluster with cert-manager and one issuer:
sudo ./pj-app-installer.sh \
--namespace dev \
--host pjapp.example.com \
--tls
Fresh cluster:
sudo ./k3s-installer.sh
sudo ./pj-app-installer.sh \
--namespace dev \
--host pjapp.example.com \
--tls \
--install-cert-manager
Ambiguous cluster or explicit issuer selection:
sudo ./pj-app-installer.sh \
--namespace dev \
--host pjapp.example.com \
--tls \
--cluster-issuer letsencrypt-prod
Explicit custom issuer creation:
sudo ./pj-app-installer.sh \
--namespace dev \
--host pjapp.example.com \
--tls \
--cluster-issuer letsencrypt-prod \
--create-cluster-issuer \
--acme-email admin@example.com \
--install-cert-manager
Subsequent upgrades:
sudo ./pj-app-updater.sh \
--namespace dev \
--host pjapp.example.com \
--tls
--hostmust be a publicly resolvable FQDN pointing to the node's IP. Let's Encrypt must reach it on port 80 for the HTTP-01 challenge.--cluster-issueris optional. If omitted, the chart auto-discovers a ClusterIssuer, or fails if the cluster is ambiguous.--acme-emaildefaults tole@johnsnowlabs.comwhen a ClusterIssuer is being created.--install-cert-managerinstalls cert-manager only if it is missing. During TLS setup, it also reuses a single existing ClusterIssuer or creates a defaultletsencrypt-prodissuer when none exists.
Installer options
These options apply to pj-app-installer.sh. For the updater, see Updater options.
| Flag | Short | Value | Default | Meaning |
|---|---|---|---|---|
--airgap | -a | none | false | Airgap mode: verify all images exist locally (skip pulling from registries). |
--eks | none | false | Managed-cluster mode for EKS: skips image pre-pull, avoids crictl, and avoids explicit --kubeconfig. | |
--efs | none | false | With --eks, provision AWS EFS/EBS CSI storage and pj-app-efs/pj-app-gp3 StorageClasses. | |
--gke | none | false | Managed-cluster mode for GKE: skips image pre-pull, avoids crictl, and avoids explicit --kubeconfig. | |
--aks | none | false | Managed-cluster mode for AKS: skips image pre-pull, avoids crictl, and avoids explicit --kubeconfig. | |
--openshift | none | false | OpenShift/ROSA mode: skips image pre-pull and configures the project, SCC bindings, and compatibility values. Requires a non-default namespace. | |
--skip-image-pull | none | false | Don’t pre-pull images. | |
--interactive-secrets | -i | none | false | Force interactive prompts for API keys (overrides env vars). |
--enable-kibana | none | false | Enable Kibana deployment (default: disabled). | |
--disable-cpg-agent | none | false | Disable CPG Agent when its private-registry credentials are unavailable. | |
--license-file | string | Path to the license file for Spark NLP Station and Resolver. | ||
--tls | none | false | Enable TLS/HTTPS ingress via cert-manager (requires --host with an FQDN). | |
--cluster-issuer | string | Use an existing cert-manager ClusterIssuer. If omitted, the chart auto-discovers one. | ||
--create-cluster-issuer | none | false | Create the named ClusterIssuer if it does not already exist. | |
--acme-email | string | Email for Let's Encrypt ACME registration. Defaults to le@johnsnowlabs.com when creating an issuer. | ||
--install-cert-manager | none | false | Install cert-manager only if it is missing. During TLS setup, it also reuses one existing issuer or creates default letsencrypt-prod when none exists. | |
--help | none | Show help message and exit. | ||
--host | -h | string | none (no host rule) | DNS hostname for the shared ingress (must be FQDN; do not pass an IP). If omitted, the script sets ingress.noHost=true, so the ingress has no host rule. |
--namespace | -n | string | default | Kubernetes namespace to create/use. |
--release | -r | string | pj-app | Helm release name. |
--values | -f | path | Additional environment-specific Helm values file; repeat for multiple files. | |
--nfs | none | false | Provision NFS-backed RWX storage for the pipeline cache on self-managed clusters. Use for multi-node K3s; incompatible with managed-cluster modes. | |
--enable-naaccr-services | none | false | Enable NAACCR Edits60 API and XML Service. | |
--enable-seed-rds | none | false | Enable the post-migration RDS seeding hook job. | |
--modes | list | all modes | Licensed product modes: comma-separated registry, deid, curation, journey. Turns the matching services on or off. See Product Modes. | |
--enable-dcs | none | false | Enable Data Curation Service; requires --dcs-tag. | |
--dcs-tag | tag | unset | Set the Data Curation Service image tag. | |
--enable-cpg-agent | none | true | Enable CPG Agent; requires its registry credentials. | |
--cpg-agent-tag=TAG | tag | 0.1.0-rc44 | Override the CPG Agent image tag. Use the equals-sign form. | |
--disable-db-hostport | none | false | Disable the relational database host port; EKS mode disables it automatically. | |
--enable-es-external | none | false | Enable Elasticsearch external access. | |
--disable-es-external | none | external access disabled | Disable Elasticsearch external access. | |
--elasticsearch-profile | small / medium / advanced | chart configuration | Select Elasticsearch sizing: small uses 2 nodes and 2 GB heap; medium uses 3 nodes and 4 GB heap; advanced uses 3 master and 2 data nodes with 8 GB data heap. Explicit heap/replica options override the profile. | |
--elasticsearch-heap | size | chart/profile value | Set Elasticsearch heap; memory limit is twice the heap size. | |
--elasticsearch-replicas | number | 2 | Set Elasticsearch master node replica count. | |
--elasticsearch-storage | size | 50Gi | Set Elasticsearch PVC size. Existing volumes require a supported storage resize procedure. | |
--with-gpu-doc-processor | none | false | Also pull the GPU document processor image, needed by jobs whose template has "Use GPU" on for document ingestion. | |
--slm-ocr | none | false | Enable HunyuanOCR service. | |
--install-gpu-operator | none | false | Install NVIDIA GPU Operator for GPU workloads. | |
--is-ami | none | false | Use AMI mode: skip credential checks and local image pre-pull. | |
--enable-licensed-services | none | false | Enable NLP Station and Resolver before the license is available in the database; services retry until it is available. | |
--k8s | none | false | Install on a self-managed Kubernetes cluster built with kubeadm. Uses /etc/kubernetes/admin.conf, or the path in KUBECONFIG if it is set, and the NGINX ingress class. Checks the cluster before it changes anything. Do not combine with --eks, --gke, --aks, --openshift, --efs, or --is-ami. | |
--slm-mode | cpu / gpu / none | cpu | Where the language model runs. none turns it off. | |
--slm-gpu-count | number | 1 | How many GPUs the language model uses. Only applies with --slm-mode gpu. | |
--llm | jsl-27b-v3 / default | chart default | Which language model to serve. jsl-27b-v3 needs --slm-mode gpu. | |
--litellm | true / false | false | Put a LiteLLM proxy in front of the language model. Needs --slm-mode cpu or gpu. | |
--doc-processor-mode | cpu / gpu | cpu | Where the document processor runs. | |
--terminology-user | string | TERMINOLOGY_USER | Terminology service user name. | |
--terminology-password | string | TERMINOLOGY_PASSWORD | Terminology service password. | |
--terminology-client-secret | string | TERMINOLOGY_CLIENT_SECRET | Terminology service client secret. | |
--terminology-keycloak-url | URL | TERMINOLOGY_KEYCLOAK_URL | Terminology service sign-in URL. |
Examples:
# HTTP only
./pj-app-installer.sh \
--host pjapp.example.com \
--namespace prod
# HTTPS with Let's Encrypt on a fresh cluster
./pj-app-installer.sh \
--host pjapp.example.com \
--namespace prod \
--tls \
--install-cert-manager
# HTTPS on an existing cluster with one issuer
./pj-app-installer.sh \
--host pjapp.example.com \
--namespace prod \
--tls
# HTTPS with an explicitly selected existing issuer
./pj-app-installer.sh \
--host pjapp.example.com \
--namespace prod \
--tls \
--cluster-issuer letsencrypt-prod
# HTTPS creating a custom issuer explicitly
./pj-app-installer.sh \
--host pjapp.example.com \
--namespace prod \
--tls \
--cluster-issuer letsencrypt-prod \
--create-cluster-issuer \
--acme-email admin@example.com \
--install-cert-manager
Environment variables
Some settings are read from environment variables instead of options. Export them before you run the installer, and use sudo -E so the script can see them:
export OPENAI_API_KEY="sk-..."
sudo -E ./pj-app-installer.sh --host pji.example.com --namespace pji
When a variable is set, it wins over the interactive prompt.
API keys (optional)
In release 2.2.2, these keys are used only by the Data Curation Service, when it uses a custom language model, and by the terminology server. You can skip them if you do not use these features. The features that use them may change in later releases.
| Variable | Description |
|---|---|
OPENAI_API_KEY | OpenAI API key. |
OLLAMA_API_KEY | Ollama API key. |
HUGGINGFACE_API_TOKEN | Hugging Face access token. |
OPENROUTER_API_KEY | OpenRouter API key. |
CHAT_BASE_URL | Base URL for the chat provider, such as https://openrouter.ai/api/v1. |
LLM_PROVIDER | The language model provider used for data curation. |
CUSTOM_LLM_API_KEY | API key for a custom, OpenAI-compatible model endpoint. |
CUSTOM_LLM_API_BASE, CUSTOM_LLM_BASE_URL | Base URL of the custom model endpoint. |
CUSTOM_LLM_MODEL | Model name at the custom endpoint. |
Terminology service
These settings are the credentials the platform uses to sign in to the terminology service. Set them with options or with environment variables. Options win over environment variables. Each value is applied only when it is set.
| Option | Environment variable | Description |
|---|---|---|
--terminology-user | TERMINOLOGY_USER | Terminology service user name. |
--terminology-password | TERMINOLOGY_PASSWORD | Terminology service password. |
--terminology-client-secret | TERMINOLOGY_CLIENT_SECRET | Terminology service client secret. |
--terminology-keycloak-url | TERMINOLOGY_KEYCLOAK_URL | Terminology service sign-in URL. |
sudo ./pj-app-installer.sh --host pji.example.com --namespace pji \
--terminology-user "user@example.com" \
--terminology-password "..." \
--terminology-client-secret "..." \
--terminology-keycloak-url "https://terminology.example.com/auth"
export TERMINOLOGY_USER="user@example.com"
export TERMINOLOGY_PASSWORD="..."
export TERMINOLOGY_CLIENT_SECRET="..."
export TERMINOLOGY_KEYCLOAK_URL="https://terminology.example.com/auth"
sudo -E ./pj-app-installer.sh --host pji.example.com --namespace pji
Storage and runtime
| Variable | Default | Description |
|---|---|---|
NFS_STORAGE_CLASS | pj-app-nfs | Name of the storage class created by --nfs. |
NFS_BACKING_STORAGE_CLASS | cluster default | Storage class that holds the NFS data. On K3s, the default is local-path. |
NFS_BACKING_SIZE | 50Gi | Size of the NFS volume. |
EFS_CLUSTER_NAME, EFS_REGION | current EKS context | EKS cluster and region used by --efs. |
AKS_FILE_STORAGE_CLASS | azurefile-csi-premium | Storage class used on AKS. |
KUBECONFIG | /etc/kubernetes/admin.conf | Path to the Kubernetes settings file used by --k8s. Set it only if your settings file is somewhere else. |
CONTAINER_RUNTIME_ENDPOINT | /run/containerd/containerd.sock, if crictl has no setting | The container runtime crictl uses to download images. |
CONTAINERD_NAMESPACE | default | The container runtime namespace the --airgap image check looks in. Set it to k8s.io for air-gapped Kubernetes installs. |
ECK_OPERATOR_CHART | none | Path to eck-operator-3.4.0.tgz for air-gapped installs, if it is not in the release folder. |
For the managed database, OpenSearch, and Databricks variables, see Managed AWS Services and Databricks.
Managed AWS services (RDS / OpenSearch)
Both pj-app-installer.sh and pj-app-updater.sh support switching from the bundled self-hosted PostgreSQL and Elasticsearch to AWS RDS, AWS OpenSearch, or both. The scripts read provider configuration from environment variables, so export them before running either script.
Replace <region> with the region for your services and use their actual endpoints. The examples enable certificate verification. Set RDS_SSL_CA to an existing, trusted CA bundle for your RDS endpoint before running the scripts. The commented alternatives disable verification for testing only; do not use them in production.
AWS RDS (replaces bundled Postgres)
export DB_PROVIDER="aws_rds"
export RDS_HOSTNAME="your-rds-endpoint.rds.amazonaws.com"
export RDS_PORT="5432"
export RDS_DB_NAME="postgres"
export RDS_USERNAME="postgres"
export RDS_PASSWORD="your-rds-password"
export RDS_REGION="<region>"
export RDS_ALLOW_INSECURE_SSL="false"
export RDS_SSL_CA="/path/to/global-bundle.pem"
# Testing only: export RDS_ALLOW_INSECURE_SSL="true"
Setting DB_PROVIDER=aws_rds automatically disables the bundled relationaldb pod and points all services (DATABASE_URL, PREPARED_DB_URL, etc.) at the RDS endpoint.
AWS OpenSearch (replaces bundled Elasticsearch)
export ES_PROVIDER="aws_opensearch"
export AWS_ES_HOST="https://your-domain.region.es.amazonaws.com"
export AWS_ES_REGION="<region>"
export AWS_ES_SERVICE="es"
export AWS_ES_USERNAME="admin"
export AWS_ES_PASSWORD="your-opensearch-password"
export AWS_ES_VERIFY_CERTS="true"
# Testing only: export AWS_ES_VERIFY_CERTS="false"
Setting ES_PROVIDER=aws_opensearch automatically disables the bundled Elasticsearch pod.
Combined example
export DB_PROVIDER="aws_rds"
export RDS_HOSTNAME="your-rds-endpoint.rds.amazonaws.com"
export RDS_PORT="5432"
export RDS_DB_NAME="postgres"
export RDS_USERNAME="postgres"
export RDS_PASSWORD="your-rds-password"
export RDS_REGION="<region>"
export RDS_ALLOW_INSECURE_SSL="false"
export RDS_SSL_CA="/path/to/global-bundle.pem"
# Testing only: export RDS_ALLOW_INSECURE_SSL="true"
export ES_PROVIDER="aws_opensearch"
export AWS_ES_HOST="https://your-domain.region.es.amazonaws.com"
export AWS_ES_REGION="<region>"
export AWS_ES_SERVICE="es"
export AWS_ES_USERNAME="admin"
export AWS_ES_PASSWORD="your-opensearch-password"
export AWS_ES_VERIFY_CERTS="true"
# Testing only: export AWS_ES_VERIFY_CERTS="false"
./pj-app-installer.sh --namespace prod --host pjapp.example.com
# or for upgrades:
./pj-app-updater.sh --namespace prod --host pjapp.example.com
Frequently asked questions
Use sudo on the K3s host. On EKS, GKE, AKS, and OpenShift, run the release scripts as the authenticated administration user, without sudo, and include the matching --eks, --gke, --aks, or --openshift flag. Using sudo there may select root's kubeconfig instead of your cluster credentials.
Uploaded documents, pipeline working files, generated exports, and related document artifacts are stored on global.sharedPvcs.docStorage. The chart supports a dynamic PVC (the default), an Ops-managed PVC, or a node-local PV for single-node K3s and on-premises installations.
Pass --tls with a --host that is a publicly resolvable FQDN. On a fresh cluster, add --install-cert-manager. Let's Encrypt must reach the host on port 80 for the HTTP-01 challenge. On every later upgrade, pass --tls again.
Yes. Export DB_PROVIDER=aws_rds with the RDS connection variables, ES_PROVIDER=aws_opensearch with the OpenSearch variables, or both, before running the installer or updater. The bundled PostgreSQL and Elasticsearch pods are then disabled.
Export the variables, then run the script with sudo -E so it receives them. Your sudo policy must allow environment preservation. When a variable is set, it takes precedence over the interactive prompt.