Skip to main content

Environment Variable Reference

All configuration for PlantPulse originates from a single place: environment variables. This page is a reference organized by category, covering the variables you'll encounter during installation and operation.

This page covers infrastructure/deployment environment variables (PP_* · DOCKER_PP_*). For application behavior properties (engine.*, websocket.*, etc.), see the Properties Reference.

1. Where are the configuration files?

The Platform runs as a Docker Compose stack, with values defined in two places.

SourceWhat's defined hereWho reads it
plantpulse-platform-docker/bin/env.shValues determined on the host — credential defaults, docker runtime, network, resource values computed on the hostOperations scripts on the host
plantpulse-platform-docker/compose/docker-compose.ymlValues actually seen by containersContainers
App containers do not read env.sh

The six apps (plantpulse-server-web · plantpulse-batch-web · plantpulse-warehouse · plantpulse-plugin-opcua-server · plantpulse-plugin-aasx-server · plantpulse-ha) do not source env.sh. The only source apps see is compose.

In other words, names that exist only in env.sh and not in compose never reach the apps, and the apps fall back to the defaults built into the jar/WAR. There was an actual incident where a site changed an identifier only in the data lake, and five apps kept using the old name (2026-08-29).

The way values are determined differs by tier.

TierWhat determines the value
plantpulse-datalakeThe value passed by compose → the container's pd receives it as ${VAR:-기본값}. The passed value wins
Six appsEnvironment variables passed by compose override the defaults built into the jar/WAR
plantpulse-certsOnly PP_TLS_* in compose is seen. The file is not sourced

Precedence — what wins over what

On the host side, the order in which bin/env.sh runs from top to bottom is the precedence order.

PrecedenceSourceWhy it wins
1 (highest)/etc/kopens/plantpulse-platform.env (secret sidecar)export VAR=값 is an unconditional assignment, so it overrides even shell exports
2/etc/kopens/platform.node.env (per-node)Sourced after the sidecar, also an unconditional assignment
3export of the calling shellOverrides :- in the defaults — but only if 1 and 2 didn't set that name
4 (lowest)${VAR:-기본값} in bin/env.shOnly when unset
The most common mistake is misunderstanding that #1 is highest

On a node where installation has completed, PP_PG_PASSWORD=새값 bin/up.sh is silently ignored. This is because the sidecar already holds that name. The only way to change the value is via bin/passwd.sh / bin/rotate-secret.sh.

🔑 The source of truth for secrets lives under /etc/kopens/

DB/messaging passwords and API keys live in the sidecar, not in the two files above

The actual values of service account passwords (PP_PG_PASSWORD · PP_CASSANDRA_PASSWORD · PP_MQ_PASSWORD, etc.) and API keys live in a sidecar file outside the repo tree.

FileStatus
/etc/kopens/plantpulse-platform.envCurrent source of truth — shared convention across the three products platform · ai · studio (plantpulse-<product>.env), permissions 0600
/opt/kopens/plantpulse-platform.envOld path (existed only 2026-08-25 ~ 08-29) — neither read nor written. If it remains, the install script reverts it to the source of truth

Check ls -l /etc/kopens/ first to see if the source of truth exists.

The sidecar overrides bin/env.sh. This is because env.sh sources the sidecar first at the very top, and all values below it are of the form ${VAR:-기본값}. So fixing a password only in env.sh has no effect — the sidecar's old value keeps winning.

No random values are generated at install time

install.sh loads the defaults from bin/env.sh into the sidecar as-is. So you must replace them during commissioningInitial Passwords

Compose requires passwords as ${PP_X:?}, so if the value is empty, the stack won't come up at all rather than booting in a half-configured state.

Don't change passwords/API keys by editing files

Fixing the sidecar alone does not complete the change. For values where the server-side account is the source of truth, such as PostgreSQL · Cassandra, the server side must also be changed, and getting the order wrong will prevent the platform from starting.

Use the dedicated procedure — Changing Passwords (Credential Rotation). This is a task you do once during commissioning and are done with.

The 🔑 mark denotes a secret value. Don't put the raw value in git, issues, or chat.

1.5 Container Runtime · Host Resources (DOCKER_*)

These are values determined by bin/env.sh. The resource values below are computed by looking at the host, not fixed defaults — because using constants would either refuse to start on smaller hosts (docker refuses if --cpus is larger than the actual cores) or get OOM-killed during boot. A per-box specified value always wins.

VariableHow it's determined
DOCKER_CMDdocker (podman also possible)
DOCKER_REGISTRYdocker.kopens.io/pp — registry prefix for the eight stack images
PP_IMAGE_TAGlatest. For releases, a fixed tag like 2026.08 is recommended
DOCKER_PP_CPUSnproc (falls back to 8 if reading fails)
DOCKER_PP_CLUSTER_CORESDOCKER_PP_CPUS - 2, minimum 4 · maximum 30. Passed to the container as PP_CLUSTER_CORES
DOCKER_PP_MEMORY90% of host RAM, minimum 8G
DOCKER_DATALAKE_MEMORY80G. If the host is smaller than that, 90% of RAM (raised from 64G on 2026-09-04 — after the archiver's spark-submit started running inside this container, it hit OOM at the 64g ceiling. Observed peak 63.9G)
DOCKER_PW_MEMORYSame value as DOCKER_DATALAKE_MEMORY — since the worker uses the same image as the data lake, it gets the same budget
DOCKER_PP_DATA_DISK_NAMETraces back the actual disk backing / via findmnt + lsblk (down to the physical disk even for LVM/dm). Falls back to sda on failure
DOCKER_PP_EXTERNAL_IPEmpty by default. Only the actual public IP in NAT environments
Don't put an invalid IP in DOCKER_PP_EXTERNAL_IP

This value flows through PP_SERVICE_IP into the SAN of the TLS certificate. If even one malformed IP is mixed in, openssl will reject the entire extension file and no certificate at all will be generated.

Network

VariableDefaultDescription
DOCKER_PP_NETWORKpp-netdocker network name
DOCKER_SUBNET10.99.0.0/24subnet (avoid overlap with other container networks)
DOCKER_GATEWAY10.99.0.1gateway
DOCKER_PP_IP10.99.0.100fixed IP of the data lake

Per-app memory limits — determined by compose

Having a separate mem_limit for each app is the primary purpose of container separation (OOM isolation). Even if one app hits its limit, other apps and the infrastructure stay alive.

VariableDefaultService
DOCKER_SERVER_MEMORY / DOCKER_SERVER_HEAP16g / 12gplantpulse-server-web
DOCKER_BATCH_MEMORY / DOCKER_BATCH_HEAP4g / 3gplantpulse-batch-web
DOCKER_WAREHOUSE_MEMORY8gplantpulse-warehouse
DOCKER_OPCUA_MEMORY4gplantpulse-plugin-opcua-server
DOCKER_AASX_MEMORY4gplantpulse-plugin-aasx-server
DOCKER_HA_MEMORY2gplantpulse-ha
DOCKER_DATALAKE_MEMORY80gplantpulse-datalake

Volumes

VariableDefaultPurpose
DOCKER_PP_DATA_VOLUMEpp-datapersistent data storage
DOCKER_PP_TEMP_VOLUMEpp-temptemporary data
DOCKER_PP_BACKUP_VOLUMEpp-backupbackup storage
DOCKER_PP_SECURITY_VOLUMEpp-securityTLS material
DOCKER_PP_PROXY_CERTS_VOLUMEpp-proxy-certsproxy certificate

install.sh one-time options

VariableEffect
SKIP_OS=1skip OS setup
SKIP_LOGIN=1skip registry login
SKIP_FW=1skip firewall configuration
DOCKER_DATA_DIRDocker data-root path (default /data1/docker-data)

2. The bare minimum to check

For a new single-server installation, there are only a few things you actually need to touch, since the installer computes resource values by looking at the host.

WhatWhereNotes
🔑 various *_PASSWORD · API keysbin/passwd.sh / bin/rotate-secret.shMust be replaced during commissioning. Editing files won't change them
PP_LANGbin/env.shFor Korean-language operation, ko (default en)
PP_TZbin/env.shFor Korea operations, keep Asia/Seoul
DOCKER_PP_EXTERNAL_IPbin/env.shOnly when behind NAT. Otherwise leave it empty
DOCKER_DATALAKE_MEMORYbin/env.shDefault 80G. Automatically lowered on smaller hosts
The PP_* from §3 onward below are "names as seen by containers"

Values such as PP_HOST_IP · PP_DATA_DIR · PP_CLUSTER_CORES are names that work inside containers, and what determines that value on the host is compose and DOCKER_* in bin/env.sh (§1.5). For example, the core count is computed by DOCKER_PP_CLUSTER_CORES and passed to the container as PP_CLUSTER_CORES.

Don't expect a fix to take effect just by changing PP_CLUSTER_CORES on the host — if it isn't passed through, a 16-vCPU box will believe it has 30 nonexistent cores and Kyuubi will never come up.

3. Platform Defaults / Network / Resources

VariableDefaultDescription
PP_HOME/opt/kopens/plantpulse-platforminstallation root (same path inside the container)
PP_SCHEMEPPdeployment identification scheme
PP_MODEMASTERnode role (MASTER / worker) — the clustering (horizontal scaling) axis
PP_TIERFULLthe layer gate this node brings up (FULL / DATALAKE / APP) — orthogonal to PP_MODE. FULL=infra+app all-in-one, DATALAKE=infra only, APP=app only (connects to remote data lake). See Two-Node Split Installation for details
PP_OPTIONS{}JSON dedicated to toggling individual services on/off. Layer-level gating has been moved to PP_TIER (see §3.3 below)
PP_HOST_IP127.0.0.1IP of this node
PP_SERVICE_IP127.0.0.1service bind IP
PP_MASTER_IP127.0.0.1the master (data lake) IP the worker/APP tier looks at. Also the source of Kafka advertised.listeners. Auto-detected as the host IP if unspecified when installing the DATALAKE tier
PP_DATALAKE_ANCHOR(derived)default IP anchor for the 5 datastore HOSTs. APP tier=PP_MASTER_IP (remote data lake), others (FULL/DATALAKE)=PP_HOST_IP (own IP). A derived value not set directly (see §3.2 below)
PP_PUBLIC_IP127.0.0.1external public IP (NAT environments)
PP_DATA_DIR/data1/pp-datadata root
PP_TEMP_DIR/data1/pp-temptemp file root
PP_BACKUP_DIR/data1/pp-backupbackup root
PP_DATA_DISK_NAMEsdbdata disk device name
PP_CLUSTER_CORES30number of cores used
PP_CLUSTER_MEMORY_BY_CORE2Gmemory allocation per core
PP_LANGendefault language
PP_TZAsia/Seoultimezone (automatically applied to all java processes)

3.1 PP_TIER — layer boot gate

PP_TIER is the gate that determines which layer this node boots. It is completely independent from the clustering axis PP_MODE (MASTER/WORKER).

PP_TIERLayers booted
FULL (default)infrastructure + application (single-box all-in-one — same as legacy installations if unspecified)
DATALAKEinfrastructure layer only (Cassandra/PG/Kafka/MQTT/Redis/MinIO/Spark/Hive/TSE/CEP/Data Gateway/Temporal/Kestra/Monitor)
APPapplication layer only (Server/Batch/Warehouse/OPC-UA/AASX/HA — connects to remote data lake)

See Two-Node Split Installation for the procedure to split the data lake/application into two boxes.

3.2 PP_DATALAKE_ANCHOR — datastore anchor (derived value)

Concept: the default hosts for PP_REDIS_HOST · PP_POSTGRES_HOST · PP_CASSANDRA_HOST · PP_MINIO_HOST · PP_TSE_HOST · PP_TEMPORAL_HOST · PP_KESTRA_HOST · PP_DATA_GATEWAY_HOST · PP_CEP_HOST all resolve to ${PP_DATALAKE_ANCHOR}.

  • In the APP tier, storage is remote (the data lake node), so PP_DATALAKE_ANCHOR = PP_MASTER_IP.
  • In others (FULL/DATALAKE), it binds to its own IP, so PP_DATALAKE_ANCHOR = PP_HOST_IP.

This value is not set directly, since it is automatically derived from PP_TIER. For a APP node, specifying only PP_MASTER_IP causes all datastore hosts to automatically point to the data lake.

3.3 PP_OPTIONS — toggling individual services on/off

PP_OPTIONS is JSON for turning individual components on and off. The default is {}, and any unspecified key is treated as true (enabled). Layer-level activation (the former use-infra / use-app) has been moved to PP_TIER.

Example) To disable the built-in Valkey and use an externally managed Redis instead:

export PP_OPTIONS='{"enable-valkey":false}'

Available enable-* keys (all default to true):

enable-messaging · enable-valkey · enable-postgres · enable-cassandra · enable-spark · enable-minio · enable-tse · enable-cep · enable-workflow · enable-data-gateway · enable-sql · enable-hive · enable-gravitino · enable-kyuubi · enable-grafana · enable-monitor

4. Datastores

VariableDefaultDescription
PP_REDIS_HOST / PP_REDIS_PORT${PP_DATALAKE_ANCHOR} / 6379Valkey/Redis (TLS listener 6380)
PP_REDIS_USER / 🔑 PP_REDIS_PASSWORDredis / —Redis account
PP_POSTGRES_HOST / PP_POSTGRES_PORT${PP_DATALAKE_ANCHOR} / 5432PostgreSQL 18 (Percona)
PP_PG_USER / 🔑 PP_PG_PASSWORDplantpulse / —PostgreSQL account
PP_CASSANDRA_HOST / PP_CASSANDRA_PORT${PP_DATALAKE_ANCHOR} / 9042Cassandra
PP_CASSANDRA_USER / 🔑 PP_CASSANDRA_PASSWORDcassandra / —Cassandra account
PP_STORAGE_PORT9042Cassandra port reused by storage layer clients
PP_MINIO_HOST / PP_MINIO_PORT${PP_DATALAKE_ANCHOR} / 9000MinIO object storage
PP_MINIO_USER / 🔑 PP_MINIO_PASSWORDminio / —MinIO account
PP_MINIO_BUCKETplantpulsedefault bucket
PP_DB_NAMEppPostgreSQL database name
PP_KEYSPACEppCassandra keyspace name
PP_HIVE_DBhive-metastore-230Hive metastore DB

5. Messaging

VariableDefaultDescription
PP_MQ_USER / 🔑 PP_MQ_PASSWORDmq / —Kafka·MQTT shared account (no dedicated Kafka account)
PP_TOPIC_PREFIXppKafka topic prefix (e.g. pp-tag-point)
PP_KAFKA_HOST / PP_KAFKA_PORTPP_MASTER_IP / 9092Kafka broker
PP_KAFKA_TLS_PORT9094Kafka external TLS
PP_KAFKA_CONTROLLER_PORT9093Kafka KRaft controller
PP_MQTT_HOST / PP_MQTT_PORTPP_MASTER_IP / 1883MQTT (HiveMQ)
PP_MQTT_TLS_PORT1884MQTT TLS
STOMP (ActiveMQ) has been retired

With browser real-time push switched to SSE, the STOMP broker was removed from the data lake image. The PP_STOMP_* variable and ports 61000/61004 are no longer served. If they remain in old firewall rules, please clean them up.

6. Processing · Time Series · Analytics · Workflow

VariableDefaultDescription
PP_CEP_HOST / PP_CEP_PORT / PP_CEP_TLS_PORT${PP_DATALAKE_ANCHOR} / 7400 / 7401CEP server
PP_CEP_PROTOCOLhttpCEP connection protocol. https recommended for distributed/containerized setups
PP_CEP_CONNECT_PORT(derived)server-web→CEP connection port. Auto-aligned to protocol (http→7400, https→7401). If specified explicitly, that value takes precedence
PP_DATA_GATEWAY_HOST / PP_DATA_GATEWAY_PORT / PP_DATA_GATEWAY_TLS_PORT${PP_DATALAKE_ANCHOR} / 5500 / 5501Data Gateway
PP_DATA_GATEWAY_PROTOCOLhttpDGW connection protocol (internal loopback defaults to http)
PP_DATA_GATEWAY_CONNECT_PORT(derived)DGW connection port. Auto-aligned to protocol (http→5500, https→5501). If specified explicitly, that value takes precedence
PP_TSE_HOST / PP_TSE_PORT / PP_TSE_TLS_PORT${PP_DATALAKE_ANCHOR} / 7800 / 7801Time Series Engine (TSE). The server listens on both http 7800 · https 7801
PP_TSE_PROTOCOLhttpTSE client connection protocol. Defaults to plaintext http since it's internal segment communication — separated from the TLS master switch (PP_TLS_ENABLED) (2026-08)
PP_TSE_CONNECT_PORT(not set)operator explicit override hook not exported by env.sh. Consumed by templates as a ${PP_TSE_CONNECT_PORT:7800} fallback
PP_TSE_JDBC_SCHEMEtsJDBC scheme. Defaults to plaintext ts (jdbc.tse.url template hardcodes ts)
PP_TSE_USER / 🔑 PP_TSE_PASSWORDtse / —TSE account
PP_ANALYTICS_USER / 🔑 PP_ANALYTICS_PASSWORDspark / —Analytics (Spark/Kyuubi client) account. Separate credentials from the Kyuubi internal metastore (PP_HIVE_*)
PP_HIVE_HOST / PP_HIVE_PORTPP_MASTER_IP / 9083Hive metastore
PP_HIVE_USER / 🔑 PP_HIVE_PASSWORDhive / —Hive account
PP_SPARK_HOST / PP_SPARK_PORTPP_MASTER_IP / 7077Spark master
PP_KYUUBI_PORT10000Kyuubi SQL gateway
PP_GRAVITINO_USER / 🔑 PP_GRAVITINO_PASSWORDgravitino / —Gravitino catalog
PP_TEMPORAL_HOST / PP_TEMPORAL_PORT${PP_DATALAKE_ANCHOR} / 7233Temporal engine (Web UI 8233)
PP_TEMPORAL_USER / 🔑 PP_TEMPORAL_PASSWORDtemporal / —Temporal account
PP_KESTRA_HOST / PP_KESTRA_PORT${PP_DATALAKE_ANCHOR} / 8380Kestra scheduler
PP_KESTRA_ADMIN_EMAIL / 🔑 PP_KESTRA_ADMIN_PASSWORDadmin@plantpulse.io / —Kestra admin
PP_KESTRA_DB_USER / 🔑 PP_KESTRA_DB_PASSWORDkestra / —Kestra meta DB

7. Plugins · Observability · Web Layer

VariableDefaultDescription
PP_OPCUA_PORT / PP_OPCUA_TCP_PORT11004OPC-UA opc.tcp protocol port
PP_OPCUA_TLS_PORT11005OPC-UA TLS
PP_AAS_V3_PORT8090AAS (BaSyx V3) API. The UI is 8091, and the Registry/AAS ports (4800/4801) are fixed in code and not changeable via env
PP_GRAFANA_PORT3000Grafana dashboard
PP_GRAFANA_ADMIN_USER / 🔑 PP_GRAFANA_ADMIN_PASSWORDadmin / —Grafana admin
PP_WEBSOCKET_PORT / PP_WEBSOCKET_PORT_SSL8000 / 8004real-time push WebSocket
PP_WEBSOCKET_USER / 🔑 PP_WEBSOCKET_SERVER_PASSWORDws / —WebSocket server account
PP_MAIL_SMTP_PORT25SMTP for sending alarm/report emails
PP_MAIL_SMTP_USER / 🔑 PP_MAIL_SMTP_PASSWORDwebmaster@kopens.com / —SMTP account
PP_MAIL_SMTP_AUTH / PP_MAIL_SMTP_STARTTLSfalse / falseSMTP auth · STARTTLS

8. TLS / SSL

VariableDefaultDescription
PP_TLS_ENABLEDtrueTLS master switch — determines the default protocol for service clients
PP_DEFAULT_PROTOCOL(derived: PP_TLS_ENABLED)default protocol for service clients. PP_TLS_ENABLED=truehttps, falsehttp. TSE, etc. inherit this value so protocol/port doesn't contradict the switch
PP_TLS_CERT_DIR/var/security/plantpulsecertificate directory
PP_TLS_DOMAINplantpulse.iocertificate domain
🔑 PP_TLS_KEYSTORE_PASSWORD / 🔑 PP_TLS_TRUSTSTORE_PASSWORDkeystore/truststore password (must be identical across all nodes)
PP_TLS_VALID_DAYS3650certificate validity period (days)
PP_TLS_SAN_IPSPP_HOST_IP,…,127.0.0.1certificate SAN IP. In production, must include the externally reachable IP (e.g. public IP)
PP_TLS_SAN_DNSlocalhost,<hostname>,<domain>certificate SAN DNS
PP_TLS_NODE_NAMESmaster worker-1 … worker-5cluster node name
PP_TLS_FORCE_REGENERATEfalseforce certificate regeneration on true

9. API Keys / AI Gateway

All have development defaults (fallback values). For production/customer delivery, be sure to replace them via Docker -e or export.

VariableDescription
🔑 PP_API_KEYinternal platform API key (server ↔ edge agent authentication)
🔑 PP_DATA_GATEWAY_API_KEYserver-web → data-gateway JDBC authentication
🔑 PP_CEP_API_KEYserver-web → CEP server X-API-Key (must match CEP server cep.api.key)
🔑 PP_FLOW_WEBHOOK_API_KEYFlow Engine external webhook authentication
PP_AI_OPENAI_ENABLEDenable AI gateway (default false)
PP_AI_OPENAI_BASE_URLOpenAI-compatible LLM proxy URL (default https://api.openai.com)
🔑 PP_AI_OPENAI_API_KEYLLM API key (required when PP_AI_OPENAI_ENABLED=true)
PP_AI_OPENAI_MODELmodel to use (default gpt-5-mini)

10. Applying changes after modification

cd /opt/kopens/plantpulse-platform-docker/bin
vi env.sh # 호스트 쪽 값 (DOCKER_* · PP_LANG · PP_TZ 등)
./restart.sh # 스택 재시작 — 준비될 때까지 대기 (종료 코드 0 = 쓸 수 있다)

If you need to change a value seen by a container, edit compose/docker-compose.yml and restart using the same method. The configuration templates are bind-mounted to /etc/kopens/conf on the host, so they can be edited directly from the host, and they persist through reinstallation.

If a change to a variable doesn't take effect, check in order.

CheckDescription
① Is the sidecar winning?If the same name exists in /etc/kopens/plantpulse-platform.env, env.sh and even shell exports lose → Precedence
② Does the name reach the app?The six apps don't read env.sh. If it's not in compose, it doesn't reach the app
③ Did you restart?Most values, including locale/timezone, only take effect at boot time
④ Is it a password?Passwords can't be changed by editing files → Password Rotation

For detailed procedures, see the Startup Guide.

Don't run env-reset.sh inside the container

env-reset.sh inside the data lake container is a development-only tool that unsets all PP_* and re-reads env.sh. Running it here erases even the values injected by compose, reverting to the defaults built into the image.