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.
| Source | What's defined here | Who reads it |
|---|---|---|
plantpulse-platform-docker/bin/env.sh | Values determined on the host — credential defaults, docker runtime, network, resource values computed on the host | Operations scripts on the host |
plantpulse-platform-docker/compose/docker-compose.yml | Values actually seen by containers | Containers |
env.shThe 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.
| Tier | What determines the value |
|---|---|
plantpulse-datalake | The value passed by compose → the container's pd receives it as ${VAR:-기본값}. The passed value wins |
| Six apps | Environment variables passed by compose override the defaults built into the jar/WAR |
plantpulse-certs | Only 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.
| Precedence | Source | Why 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 |
| 3 | export of the calling shell | Overrides :- in the defaults — but only if 1 and 2 didn't set that name |
| 4 (lowest) | ${VAR:-기본값} in bin/env.sh | Only when unset |
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/
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.
| File | Status |
|---|---|
/etc/kopens/plantpulse-platform.env | Current source of truth — shared convention across the three products platform · ai · studio (plantpulse-<product>.env), permissions 0600 |
/opt/kopens/plantpulse-platform.env | Old 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.
install.sh loads the defaults from bin/env.sh into the sidecar as-is. So you must replace them during commissioning → Initial 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.
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.
| Variable | How it's determined |
|---|---|
DOCKER_CMD | docker (podman also possible) |
DOCKER_REGISTRY | docker.kopens.io/pp — registry prefix for the eight stack images |
PP_IMAGE_TAG | latest. For releases, a fixed tag like 2026.08 is recommended |
DOCKER_PP_CPUS | nproc (falls back to 8 if reading fails) |
DOCKER_PP_CLUSTER_CORES | DOCKER_PP_CPUS - 2, minimum 4 · maximum 30. Passed to the container as PP_CLUSTER_CORES |
DOCKER_PP_MEMORY | 90% of host RAM, minimum 8G |
DOCKER_DATALAKE_MEMORY | 80G. 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_MEMORY | Same 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_NAME | Traces 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_IP | Empty by default. Only the actual public IP in NAT environments |
DOCKER_PP_EXTERNAL_IPThis 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
| Variable | Default | Description |
|---|---|---|
DOCKER_PP_NETWORK | pp-net | docker network name |
DOCKER_SUBNET | 10.99.0.0/24 | subnet (avoid overlap with other container networks) |
DOCKER_GATEWAY | 10.99.0.1 | gateway |
DOCKER_PP_IP | 10.99.0.100 | fixed 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.
| Variable | Default | Service |
|---|---|---|
DOCKER_SERVER_MEMORY / DOCKER_SERVER_HEAP | 16g / 12g | plantpulse-server-web |
DOCKER_BATCH_MEMORY / DOCKER_BATCH_HEAP | 4g / 3g | plantpulse-batch-web |
DOCKER_WAREHOUSE_MEMORY | 8g | plantpulse-warehouse |
DOCKER_OPCUA_MEMORY | 4g | plantpulse-plugin-opcua-server |
DOCKER_AASX_MEMORY | 4g | plantpulse-plugin-aasx-server |
DOCKER_HA_MEMORY | 2g | plantpulse-ha |
DOCKER_DATALAKE_MEMORY | 80g | plantpulse-datalake |
Volumes
| Variable | Default | Purpose |
|---|---|---|
DOCKER_PP_DATA_VOLUME | pp-data | persistent data storage |
DOCKER_PP_TEMP_VOLUME | pp-temp | temporary data |
DOCKER_PP_BACKUP_VOLUME | pp-backup | backup storage |
DOCKER_PP_SECURITY_VOLUME | pp-security | TLS material |
DOCKER_PP_PROXY_CERTS_VOLUME | pp-proxy-certs | proxy certificate |
install.sh one-time options
| Variable | Effect |
|---|---|
SKIP_OS=1 | skip OS setup |
SKIP_LOGIN=1 | skip registry login |
SKIP_FW=1 | skip firewall configuration |
DOCKER_DATA_DIR | Docker 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.
| What | Where | Notes |
|---|---|---|
🔑 various *_PASSWORD · API keys | bin/passwd.sh / bin/rotate-secret.sh | Must be replaced during commissioning. Editing files won't change them |
PP_LANG | bin/env.sh | For Korean-language operation, ko (default en) |
PP_TZ | bin/env.sh | For Korea operations, keep Asia/Seoul |
DOCKER_PP_EXTERNAL_IP | bin/env.sh | Only when behind NAT. Otherwise leave it empty |
DOCKER_DATALAKE_MEMORY | bin/env.sh | Default 80G. Automatically lowered on smaller hosts |
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
| Variable | Default | Description |
|---|---|---|
PP_HOME | /opt/kopens/plantpulse-platform | installation root (same path inside the container) |
PP_SCHEME | PP | deployment identification scheme |
PP_MODE | MASTER | node role (MASTER / worker) — the clustering (horizontal scaling) axis |
PP_TIER | FULL | the 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_IP | 127.0.0.1 | IP of this node |
PP_SERVICE_IP | 127.0.0.1 | service bind IP |
PP_MASTER_IP | 127.0.0.1 | the 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_IP | 127.0.0.1 | external public IP (NAT environments) |
PP_DATA_DIR | /data1/pp-data | data root |
PP_TEMP_DIR | /data1/pp-temp | temp file root |
PP_BACKUP_DIR | /data1/pp-backup | backup root |
PP_DATA_DISK_NAME | sdb | data disk device name |
PP_CLUSTER_CORES | 30 | number of cores used |
PP_CLUSTER_MEMORY_BY_CORE | 2G | memory allocation per core |
PP_LANG | en | default language |
PP_TZ | Asia/Seoul | timezone (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_TIER | Layers booted |
|---|---|
FULL (default) | infrastructure + application (single-box all-in-one — same as legacy installations if unspecified) |
DATALAKE | infrastructure layer only (Cassandra/PG/Kafka/MQTT/Redis/MinIO/Spark/Hive/TSE/CEP/Data Gateway/Temporal/Kestra/Monitor) |
APP | application 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_HOSTall resolve to${PP_DATALAKE_ANCHOR}.
- In the
APPtier, storage is remote (the data lake node), soPP_DATALAKE_ANCHOR = PP_MASTER_IP.- In others (
FULL/DATALAKE), it binds to its own IP, soPP_DATALAKE_ANCHOR = PP_HOST_IP.This value is not set directly, since it is automatically derived from
PP_TIER. For aAPPnode, specifying onlyPP_MASTER_IPcauses 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
| Variable | Default | Description |
|---|---|---|
PP_REDIS_HOST / PP_REDIS_PORT | ${PP_DATALAKE_ANCHOR} / 6379 | Valkey/Redis (TLS listener 6380) |
PP_REDIS_USER / 🔑 PP_REDIS_PASSWORD | redis / — | Redis account |
PP_POSTGRES_HOST / PP_POSTGRES_PORT | ${PP_DATALAKE_ANCHOR} / 5432 | PostgreSQL 18 (Percona) |
PP_PG_USER / 🔑 PP_PG_PASSWORD | plantpulse / — | PostgreSQL account |
PP_CASSANDRA_HOST / PP_CASSANDRA_PORT | ${PP_DATALAKE_ANCHOR} / 9042 | Cassandra |
PP_CASSANDRA_USER / 🔑 PP_CASSANDRA_PASSWORD | cassandra / — | Cassandra account |
PP_STORAGE_PORT | 9042 | Cassandra port reused by storage layer clients |
PP_MINIO_HOST / PP_MINIO_PORT | ${PP_DATALAKE_ANCHOR} / 9000 | MinIO object storage |
PP_MINIO_USER / 🔑 PP_MINIO_PASSWORD | minio / — | MinIO account |
PP_MINIO_BUCKET | plantpulse | default bucket |
PP_DB_NAME | pp | PostgreSQL database name |
PP_KEYSPACE | pp | Cassandra keyspace name |
PP_HIVE_DB | hive-metastore-230 | Hive metastore DB |
5. Messaging
| Variable | Default | Description |
|---|---|---|
PP_MQ_USER / 🔑 PP_MQ_PASSWORD | mq / — | Kafka·MQTT shared account (no dedicated Kafka account) |
PP_TOPIC_PREFIX | pp | Kafka topic prefix (e.g. pp-tag-point) |
PP_KAFKA_HOST / PP_KAFKA_PORT | PP_MASTER_IP / 9092 | Kafka broker |
PP_KAFKA_TLS_PORT | 9094 | Kafka external TLS |
PP_KAFKA_CONTROLLER_PORT | 9093 | Kafka KRaft controller |
PP_MQTT_HOST / PP_MQTT_PORT | PP_MASTER_IP / 1883 | MQTT (HiveMQ) |
PP_MQTT_TLS_PORT | 1884 | MQTT TLS |
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
| Variable | Default | Description |
|---|---|---|
PP_CEP_HOST / PP_CEP_PORT / PP_CEP_TLS_PORT | ${PP_DATALAKE_ANCHOR} / 7400 / 7401 | CEP server |
PP_CEP_PROTOCOL | http | CEP 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 / 5501 | Data Gateway |
PP_DATA_GATEWAY_PROTOCOL | http | DGW 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 / 7801 | Time Series Engine (TSE). The server listens on both http 7800 · https 7801 |
PP_TSE_PROTOCOL | http | TSE 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_SCHEME | ts | JDBC scheme. Defaults to plaintext ts (jdbc.tse.url template hardcodes ts) |
PP_TSE_USER / 🔑 PP_TSE_PASSWORD | tse / — | TSE account |
PP_ANALYTICS_USER / 🔑 PP_ANALYTICS_PASSWORD | spark / — | Analytics (Spark/Kyuubi client) account. Separate credentials from the Kyuubi internal metastore (PP_HIVE_*) |
PP_HIVE_HOST / PP_HIVE_PORT | PP_MASTER_IP / 9083 | Hive metastore |
PP_HIVE_USER / 🔑 PP_HIVE_PASSWORD | hive / — | Hive account |
PP_SPARK_HOST / PP_SPARK_PORT | PP_MASTER_IP / 7077 | Spark master |
PP_KYUUBI_PORT | 10000 | Kyuubi SQL gateway |
PP_GRAVITINO_USER / 🔑 PP_GRAVITINO_PASSWORD | gravitino / — | Gravitino catalog |
PP_TEMPORAL_HOST / PP_TEMPORAL_PORT | ${PP_DATALAKE_ANCHOR} / 7233 | Temporal engine (Web UI 8233) |
PP_TEMPORAL_USER / 🔑 PP_TEMPORAL_PASSWORD | temporal / — | Temporal account |
PP_KESTRA_HOST / PP_KESTRA_PORT | ${PP_DATALAKE_ANCHOR} / 8380 | Kestra scheduler |
PP_KESTRA_ADMIN_EMAIL / 🔑 PP_KESTRA_ADMIN_PASSWORD | admin@plantpulse.io / — | Kestra admin |
PP_KESTRA_DB_USER / 🔑 PP_KESTRA_DB_PASSWORD | kestra / — | Kestra meta DB |
7. Plugins · Observability · Web Layer
| Variable | Default | Description |
|---|---|---|
PP_OPCUA_PORT / PP_OPCUA_TCP_PORT | 11004 | OPC-UA opc.tcp protocol port |
PP_OPCUA_TLS_PORT | 11005 | OPC-UA TLS |
PP_AAS_V3_PORT | 8090 | AAS (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_PORT | 3000 | Grafana dashboard |
PP_GRAFANA_ADMIN_USER / 🔑 PP_GRAFANA_ADMIN_PASSWORD | admin / — | Grafana admin |
PP_WEBSOCKET_PORT / PP_WEBSOCKET_PORT_SSL | 8000 / 8004 | real-time push WebSocket |
PP_WEBSOCKET_USER / 🔑 PP_WEBSOCKET_SERVER_PASSWORD | ws / — | WebSocket server account |
PP_MAIL_SMTP_PORT | 25 | SMTP for sending alarm/report emails |
PP_MAIL_SMTP_USER / 🔑 PP_MAIL_SMTP_PASSWORD | webmaster@kopens.com / — | SMTP account |
PP_MAIL_SMTP_AUTH / PP_MAIL_SMTP_STARTTLS | false / false | SMTP auth · STARTTLS |
8. TLS / SSL
| Variable | Default | Description |
|---|---|---|
PP_TLS_ENABLED | true | TLS master switch — determines the default protocol for service clients |
PP_DEFAULT_PROTOCOL | (derived: PP_TLS_ENABLED) | default protocol for service clients. PP_TLS_ENABLED=true→https, false→http. TSE, etc. inherit this value so protocol/port doesn't contradict the switch |
PP_TLS_CERT_DIR | /var/security/plantpulse | certificate directory |
PP_TLS_DOMAIN | plantpulse.io | certificate domain |
🔑 PP_TLS_KEYSTORE_PASSWORD / 🔑 PP_TLS_TRUSTSTORE_PASSWORD | — | keystore/truststore password (must be identical across all nodes) |
PP_TLS_VALID_DAYS | 3650 | certificate validity period (days) |
PP_TLS_SAN_IPS | PP_HOST_IP,…,127.0.0.1 | certificate SAN IP. In production, must include the externally reachable IP (e.g. public IP) |
PP_TLS_SAN_DNS | localhost,<hostname>,<domain> | certificate SAN DNS |
PP_TLS_NODE_NAMES | master worker-1 … worker-5 | cluster node name |
PP_TLS_FORCE_REGENERATE | false | force 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
-eorexport.
| Variable | Description |
|---|---|
🔑 PP_API_KEY | internal platform API key (server ↔ edge agent authentication) |
🔑 PP_DATA_GATEWAY_API_KEY | server-web → data-gateway JDBC authentication |
🔑 PP_CEP_API_KEY | server-web → CEP server X-API-Key (must match CEP server cep.api.key) |
🔑 PP_FLOW_WEBHOOK_API_KEY | Flow Engine external webhook authentication |
PP_AI_OPENAI_ENABLED | enable AI gateway (default false) |
PP_AI_OPENAI_BASE_URL | OpenAI-compatible LLM proxy URL (default https://api.openai.com) |
🔑 PP_AI_OPENAI_API_KEY | LLM API key (required when PP_AI_OPENAI_ENABLED=true) |
PP_AI_OPENAI_MODEL | model 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.
| Check | Description |
|---|---|
| ① 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.
env-reset.sh inside the containerenv-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.