Skip to main content

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 existingClaim empty and local.enabled false. Set storageClass, size, and accessModes in an environment values file. An empty storageClass uses the cluster default.
  • Ops-managed PVC: set create: false and existingClaim to a claim in the release namespace. Use an RWX-capable backend for multi-node deployments.
  • Node-local PV: set local.enabled: true, local.path, and local.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 the Retain reclaim 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
TLS prerequisites
  • --host must 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-issuer is optional. If omitted, the chart auto-discovers a ClusterIssuer, or fails if the cluster is ambiguous.
  • --acme-email defaults to le@johnsnowlabs.com when a ClusterIssuer is being created.
  • --install-cert-manager installs cert-manager only if it is missing. During TLS setup, it also reuses a single existing ClusterIssuer or creates a default letsencrypt-prod issuer when none exists.

Installer options​

These options apply to pj-app-installer.sh. For the updater, see Updater options.

FlagShortValueDefaultMeaning
--airgap-anonefalseAirgap mode: verify all images exist locally (skip pulling from registries).
--eks nonefalseManaged-cluster mode for EKS: skips image pre-pull, avoids crictl, and avoids explicit --kubeconfig.
--efs nonefalseWith --eks, provision AWS EFS/EBS CSI storage and pj-app-efs/pj-app-gp3 StorageClasses.
--gke nonefalseManaged-cluster mode for GKE: skips image pre-pull, avoids crictl, and avoids explicit --kubeconfig.
--aks nonefalseManaged-cluster mode for AKS: skips image pre-pull, avoids crictl, and avoids explicit --kubeconfig.
--openshift nonefalseOpenShift/ROSA mode: skips image pre-pull and configures the project, SCC bindings, and compatibility values. Requires a non-default namespace.
--skip-image-pull nonefalseDon’t pre-pull images.
--interactive-secrets-inonefalseForce interactive prompts for API keys (overrides env vars).
--enable-kibana nonefalseEnable Kibana deployment (default: disabled).
--disable-cpg-agent nonefalseDisable CPG Agent when its private-registry credentials are unavailable.
--license-file string Path to the license file for Spark NLP Station and Resolver.
--tls nonefalseEnable 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 nonefalseCreate 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 nonefalseInstall 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-hstringnone (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-nstringdefaultKubernetes namespace to create/use.
--release-rstringpj-appHelm release name.
--values-fpath Additional environment-specific Helm values file; repeat for multiple files.
--nfsnonefalseProvision NFS-backed RWX storage for the pipeline cache on self-managed clusters. Use for multi-node K3s; incompatible with managed-cluster modes.
--enable-naaccr-servicesnonefalseEnable NAACCR Edits60 API and XML Service.
--enable-seed-rdsnonefalseEnable the post-migration RDS seeding hook job.
--modes listall modesLicensed product modes: comma-separated registry, deid, curation, journey. Turns the matching services on or off. See Product Modes.
--enable-dcsnonefalseEnable Data Curation Service; requires --dcs-tag.
--dcs-tagtagunsetSet the Data Curation Service image tag.
--enable-cpg-agentnonetrueEnable CPG Agent; requires its registry credentials.
--cpg-agent-tag=TAGtag0.1.0-rc44Override the CPG Agent image tag. Use the equals-sign form.
--disable-db-hostportnonefalseDisable the relational database host port; EKS mode disables it automatically.
--enable-es-externalnonefalseEnable Elasticsearch external access.
--disable-es-externalnoneexternal access disabledDisable Elasticsearch external access.
--elasticsearch-profilesmall / medium / advancedchart configurationSelect 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-heapsizechart/profile valueSet Elasticsearch heap; memory limit is twice the heap size.
--elasticsearch-replicasnumber2Set Elasticsearch master node replica count.
--elasticsearch-storagesize50GiSet Elasticsearch PVC size. Existing volumes require a supported storage resize procedure.
--with-gpu-doc-processornonefalseAlso pull the GPU document processor image, needed by jobs whose template has "Use GPU" on for document ingestion.
--slm-ocrnonefalseEnable HunyuanOCR service.
--install-gpu-operatornonefalseInstall NVIDIA GPU Operator for GPU workloads.
--is-aminonefalseUse AMI mode: skip credential checks and local image pre-pull.
--enable-licensed-servicesnonefalseEnable NLP Station and Resolver before the license is available in the database; services retry until it is available.
--k8snonefalseInstall 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-modecpu / gpu / nonecpuWhere the language model runs. none turns it off.
--slm-gpu-countnumber1How many GPUs the language model uses. Only applies with --slm-mode gpu.
--llmjsl-27b-v3 / defaultchart defaultWhich language model to serve. jsl-27b-v3 needs --slm-mode gpu.
--litellmtrue / falsefalsePut a LiteLLM proxy in front of the language model. Needs --slm-mode cpu or gpu.
--doc-processor-modecpu / gpucpuWhere the document processor runs.
--terminology-userstringTERMINOLOGY_USERTerminology service user name.
--terminology-passwordstringTERMINOLOGY_PASSWORDTerminology service password.
--terminology-client-secretstringTERMINOLOGY_CLIENT_SECRETTerminology service client secret.
--terminology-keycloak-urlURLTERMINOLOGY_KEYCLOAK_URLTerminology 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.

VariableDescription
OPENAI_API_KEYOpenAI API key.
OLLAMA_API_KEYOllama API key.
HUGGINGFACE_API_TOKENHugging Face access token.
OPENROUTER_API_KEYOpenRouter API key.
CHAT_BASE_URLBase URL for the chat provider, such as https://openrouter.ai/api/v1.
LLM_PROVIDERThe language model provider used for data curation.
CUSTOM_LLM_API_KEYAPI key for a custom, OpenAI-compatible model endpoint.
CUSTOM_LLM_API_BASE, CUSTOM_LLM_BASE_URLBase URL of the custom model endpoint.
CUSTOM_LLM_MODELModel 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.

OptionEnvironment variableDescription
--terminology-userTERMINOLOGY_USERTerminology service user name.
--terminology-passwordTERMINOLOGY_PASSWORDTerminology service password.
--terminology-client-secretTERMINOLOGY_CLIENT_SECRETTerminology service client secret.
--terminology-keycloak-urlTERMINOLOGY_KEYCLOAK_URLTerminology service sign-in URL.
With options
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"
With environment variables
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​

VariableDefaultDescription
NFS_STORAGE_CLASSpj-app-nfsName of the storage class created by --nfs.
NFS_BACKING_STORAGE_CLASScluster defaultStorage class that holds the NFS data. On K3s, the default is local-path.
NFS_BACKING_SIZE50GiSize of the NFS volume.
EFS_CLUSTER_NAME, EFS_REGIONcurrent EKS contextEKS cluster and region used by --efs.
AKS_FILE_STORAGE_CLASSazurefile-csi-premiumStorage class used on AKS.
KUBECONFIG/etc/kubernetes/admin.confPath 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 settingThe container runtime crictl uses to download images.
CONTAINERD_NAMESPACEdefaultThe container runtime namespace the --airgap image check looks in. Set it to k8s.io for air-gapped Kubernetes installs.
ECK_OPERATOR_CHARTnonePath 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.