Skip to main content

Docker Installation

Overview

This page covers manual (step-by-step) installation of the PlantPulse Platform as a Docker Compose stack, as well as air-gapped network installation.

For typical environments, we recommend the one-line installation. The one-line installation handles everything at once — package download, environment auto-detection, installation, and boot verification. Use the manual procedure on this page only in the following cases:

  • When you need to review and approve each installation step yourself (security review, change management procedures, etc.)
  • When you need to explicitly specify environment variables (bin/env.sh) rather than relying on auto-detected values
  • When installing in an air-gapped network environment with no internet access → Air-gapped network installation

What Gets Installed — Nine Containers

The Platform runs as a single docker compose stack. The source of truth is compose/docker-compose.yml, and all operational scripts go through this file to manage containers.

ContainerLayerRole
plantpulse-certsCertificatesA one-shot that bakes TLS material and then exits. A normal healthy state is Exited (0)
plantpulse-datalakeInfrastructureStorage, messaging, analytics, CEP, SQL, monitoring
plantpulse-server-webApplicationWeb console
plantpulse-batch-webApplicationBatch
plantpulse-warehouseApplicationData warehouse
plantpulse-plugin-opcua-serverApplicationOPC-UA server plugin (11004 / 11005)
plantpulse-plugin-aasx-serverApplicationAASX server plugin
plantpulse-haApplicationRedundancy recovery daemon (10210)
plantpulse-proxyEdgeThe only entry point users touch (80 / 443 / 1883 / 1884)
There Are Eight Always-On Containers

plantpulse-certs is a one-shot that creates certificates and then exits on its own, so Exited (0) is normal for it. In docker ps, it's normal for the remaining eight to show as (healthy). Don't mistake the one-shot for a "dead container" — status.sh and ops-check.sh judge only this container by its exit code.

There is also a plantpulse-mirror-maker for mirroring defined in the compose file, but it's tied to the mirror profile and does not start by default.

The Old Single-Container Configuration Is Gone

Before 2026-08-29, you could choose a "monolith" configuration where all components ran together in a single plantpulse-platform container. This configuration has been discontinued, and the selector variable (PP_TOPOLOGY) and its compose file have been removed along with it. There's nothing left to choosebin/up.sh brings up the stack shown above.

Commands like docker logs plantpulse-platform that remain in old runbooks will not work, since no container by that name exists. Replacement commands can be found in the Operations Command Summary.

The primary purpose of splitting into per-app containers is OOM isolation. Even if one app exhausts its memory, other apps and the infrastructure stay alive, and you can restart or roll back at the individual app level.

Prerequisites

Before starting the installation, please verify the following:

ItemRequirement
Account privilegesroot (or sudo privileges). The installation script configures OS settings and the Docker daemon
Operating SystemRHEL/Rocky/Oracle Linux 8·9, Ubuntu 20.04+, Amazon Linux 2/2023
DockerCompose v2 is required (docker compose — the hyphen-less form). If install.sh is missing, it will be installed
Data diskWe recommend mounting a large-capacity disk at /data1 — Docker data (/data1/docker-data) and platform data volumes use this path
Registry accessHTTPS access must be available to docker.kopens.io (image registry) and product.kopens.io (installation packages). If access is blocked, please use the air-gapped network installation
Registry credentialsdocker.kopens.io login credentials (issued by the KOPENS operations team)

Disk path note: If there is no separate data disk, creating a /data1 directory on the root disk will still work, but for production environments we recommend mounting a dedicated disk at /data1.

Manual Installation Procedure

Step 1: Download the Installation Package

Download the installation package (tar.gz) from the download server and extract it to the standard path. There is no need to clone via Git or install any separate tools.

sudo -i

# 표준 설치 경로 생성 후 패키지 다운로드 + 압축 해제
mkdir -p /opt/kopens/plantpulse-platform-docker
curl -fsSL "https://product.kopens.io/plantpulse-platform/plantpulse-platform-docker.tar.gz" \
| tar -xz -C /opt/kopens/plantpulse-platform-docker --strip-components=1

cd /opt/kopens/plantpulse-platform-docker/bin
chmod +x *.sh tools/*.sh

Within the package, there are two locations that are actually used.

LocationContents
bin/All installation/operation scripts
compose/The stack source of truth docker-compose.yml and the cluster worker overlay

Step 2: Preflight Check

preflight.sh checks whether installation is possible without making any changes to the system at all.

./preflight.sh

Items checked:

  • Whether Docker is installed and the daemon is running (fine even if not installed — install.sh will install it)
  • Whether the parent path of the Docker data-root (/data1) exists
  • Whether the secrets sidecar (/etc/kopens/plantpulse-platform.env) exists
  • Whether the main operational ports (80, 443, 7443, 4949, 4950) are in use

If only OK appears, proceed to the next step. If there's a ERROR, refer to Troubleshooting. (WARN is informational only and does not block installation.)

Step 3: Review Environment Variables (env.sh)

bin/env.sh is the source of truth for host-side configuration. The values the containers actually see are determined by compose/docker-compose.yml — see the Environment Variable Reference for details on the relationship between the two.

vi env.sh
Most Resource Values Are "Calculated" — There's Little to Change

env.sh examines the host and determines CPU, memory, and disk allocations on its own. These are not fixed defaults, so small boxes get small values even without any manual intervention. A value specified per-box always wins.

VariableHow It's DeterminedWhen to Change It
DOCKER_PP_CPUSnproc (falls back to 8 if the read fails)When you want to give the container fewer cores
DOCKER_PP_CLUSTER_CORESDOCKER_PP_CPUS - 2, minimum 4 · maximum 30Usually left as-is
DOCKER_PP_MEMORY90% of host RAM, minimum 8GWhen you want to leave more headroom for the OS
DOCKER_DATALAKE_MEMORY80G. If the host has less than that, 90% of RAMAdjusting the data lake ceiling
DOCKER_PP_DATA_DISK_NAMEAutomatically traces back to the actual disk backing / (falls back to sda on failure)When auto-detection is wrong
PP_LANGenSet ko for Korean-language operations
PP_TZAsia/SeoulOverseas boxes
DOCKER_PP_EXTERNAL_IPEmpty by defaultOnly needed when you must advertise an external public IP in a NAT environment
Do Not Put an Invalid IP into DOCKER_PP_EXTERNAL_IP

This value flows through PP_SERVICE_IP into the SAN list of the TLS certificate. If even one improperly formatted IP is mixed in, openssl will reject the entire extension file, resulting in no certificate being generated at all, and the stack will fail to start. If you're not behind NAT, leave it empty — that's the default.

You can check the current server's values with the following command.

hostname -I | awk '{print $1}' # 서버 IP
free -g | awk '/^Mem:/{print $2"G"}' # 전체 메모리
nproc # CPU 코어 수
lsblk # 디스크 이름 (sda, sdb, nvme0n1 …)

On Korean-language operational boxes, usually only two lines need to be changed.

export PP_LANG=ko
export PP_TZ=Asia/Seoul
Locale Settings Only Take Effect at Boot Time

PP_LANG / PP_TZ are fixed at JVM boot time. To change them on an already-running stack, ./restart.sh is required. Also, since Cassandra time series data is ingested using KST epoch, please keep Asia/Seoul for Korean operations.

Step 4: Run the Installation (install.sh)

sudo ./install.sh

A single install.sh automatically performs the entire pre-installation process.

  1. OS Detection and System Configuration — file limits, kernel parameters (sysctl), time synchronization (chrony), switching SELinux to permissive, disabling swap
  2. Docker Engine Installation — installed via the OS-specific package manager + configures data-root as /data1/docker-data (Compose v2 is also installed if missing)
  3. Firewall Configuration — opens public ports (80/443/7443/4949/4950) and restricts internal ports to only be accessible from private network ranges
  4. Service Credential Generation — creates the /etc/kopens/plantpulse-platform.env secrets sidecar (permission 0600, does not overwrite existing values on re-run)
  5. Registry Login — enters docker.kopens.io credentials (automatically skipped if already logged in)
  6. Stack Startup — create network/volumes → pull images → seed configuration templates → docker compose up -d

One-time options for installation (specify via environment variable only if needed):

OptionEffect
SKIP_OS=1Skip OS configuration and Docker installation (when Docker is already installed and running)
SKIP_LOGIN=1Skip registry login (when already logged in or images are already local)
SKIP_FW=1Skip firewall configuration (when the firewall is managed separately)
DOCKER_DATA_DIR=<path>Change the Docker data-root (default /data1/docker-data)
# 예: Docker가 이미 설치된 서버
SKIP_OS=1 sudo -E ./install.sh
To Replace Credentials, Do It "Before" Installation

Because the secrets sidecar (/etc/kopens/plantpulse-platform.env) is an unconditional assignment in the form export VAR=값, it overrides shell exports. On a node where installation has already completed, PP_PG_PASSWORD=... ./up.sh is silently ignored. For pre-delivery replacement, do it via export PP_*_PASSWORD=... before installation; after installation completes, use passwd.sh from Password Rotation instead.

Step 5: Startup and Boot Verification

install.sh brings up the stack and returns immediately. You need to separately verify that boot has completed.

./up.sh

up.sh is idempotent, so it's safe even if the stack is already up, and it waits until the stack is ready. An exit code of 0 does not mean "the command succeeded" — it means "it's now usable."

Environment VariableDefaultMeaning
PP_READY_TIMEOUT900Readiness wait ceiling (seconds)
PP_READY_INTERVAL15Check interval (seconds)
PP_WAIT=0Do not wait. In this case, 0 does not mean readiness is complete
The First Clean Install Takes 15–18 Minutes

Starting the processes themselves takes only 3–5 minutes (JVM warm-up), but it takes 15–18 minutes for all components to stabilize. Cassandra schema migration and stabilization is the slowest part. Restarts are much faster since the schema already exists.

Measured figures (2026-08-31, 32 vCPU / 128GiB): data lake 217 seconds, web server 316 seconds.

You can also run the readiness check yourself directly.

./stack-verify-boot.sh

This script looks at the entire stack, not just a single container — it checks whether the one-shot exited normally, whether the data lake and apps are running·healthy, and even whether the proxy is serving on 443.

Step 6: Status and Operational Check

./status.sh # 0 = 정상 / 2 = 비정상
./ops-check.sh

status.sh summarizes the service list, container status, health, and volumes. The exit code is a contract — you can use it directly in automation.

JudgmentWhat It Considers Abnormal
Cannot read the service listcompose resolution failed or docker is inaccessible
No container exists for a service declared by composeNot started
An always-on container is not runningOne-shots (plantpulse-certs) are excluded
Health of an always-on container is unhealthystarting / none hold judgment
Health API is not OKProbed from inside the data lake

Volumes and conf are reported only and not factored into the exit code — because in a 2-node split installation (PP_TIER=APP), it's normal for some of them to be missing.

ops-check.sh additionally scans recent critical logs (OOM, FATAL, SSL errors, etc.) on top of this.

Health Check

The monitor API is served by the plantpulse-datalake container. The command differs depending on where you're checking from.

# 컨테이너 안에서 — 어떤 구성에서도 동작하는 방법
docker exec plantpulse-datalake curl -kfsS https://127.0.0.1:4950/api/health | jq

# 호스트/외부에서 — 4950 이 publish 되어 있습니다
curl -kfsS https://<server-ip>:4950/api/health | jq

If "status" is OK or WARN, it's within normal range; if it's FAIL, that's a failure.

You Cannot View Health on Other Ports

The console and health API are served on both ports4950 (HTTPS) and 4949 (plaintext HTTP). It's the same console, same API, differing only in scheme. 4949 no longer redirects to 4950.

4949 is plaintext — login passwords and session cookies travel unencrypted. On untrusted networks, use 4950. 4949 is an option for boxes where the self-signed certificate warning actually stops operators in their tracks.

Installation Artifacts

Networks and Volumes

ResourceNamePurpose
Networkpp-netA dedicated Docker network for the Platform (default 10.99.0.0/24, gateway 10.99.0.1)
Volumepp-dataPersistent data storage (Cassandra, PostgreSQL, Kafka, etc.)
Volumepp-tempTemporary data (Spark, Hive workspace)
Volumepp-backupBackup storage
Volumepp-securityTLS certificates/keystores. Written to by plantpulse-certs, treated as read-only by the rest
Volumepp-proxy-certsCertificate the proxy uses on 443

Volumes are always preserved even if containers are stopped or removed, so data persists across updates and reinstallations.

Host Directories

PathPurpose
/opt/kopens/plantpulse-platform-dockerInstallation/operation scripts and the compose source of truth
/etc/kopens/confPlatform configuration templates (host bind mount). Automatically seeded from the image on first run; operators can edit them directly on the host afterward, and they persist across reinstalls
/etc/kopens/plantpulse-platform.envService credential secrets sidecar (permission 0600)
/etc/kopens/platform.node.envPer-node identity (PP_TIER, etc.). Do not copy between nodes
/etc/kopens/caShared cluster CA (used in 2-node split installations)

Connection Addresses

PurposeAddress
Web consolehttp://<server-ip>/ · https://<server-ip>/ — served by plantpulse-proxy
Admin UIhttps://<server-ip>:7443
Monitor UI · Healthhttps://<server-ip>:4950/api/health
MQTT<server-ip>:1883 (plaintext) · <server-ip>:1884 (TLS)
OPC-UA<server-ip>:11004 · <server-ip>:11005

Security note: After the first login to the web console, be sure to change the default admin password. → Initial Password

Operations Command Summary

Commands used for day-to-day operations. All are run from /opt/kopens/plantpulse-platform-docker/bin/. All verbs support --help.

TaskCommandNotes
Status check./status.shSummary of services / health / volumes. 0=normal / 2=abnormal
Operational check./ops-check.shContainer health + health API + recent critical logs
Start./up.shIdempotent. 0 = ready
Stop./down.shPreserves state
Restart./restart.shgraceful drain → stop → start → wait for readiness
View logs./logs.sh [서비스]Prints the last 200 lines and exits. To follow, use -f. If no argument, targets all containers. See --list for the list
Enter a container./shell.sh [서비스]Defaults to the data lake if no argument
Update images./update.shpull + recreate. Automatically rolls back to the previous image on failure
Full removal./remove.shRemoves containers only (volumes/config preserved). RM_IMAGE=1 / RM_NETWORK=1
Backup./backup.sh [볼륨 …]Default pp-data · pp-security
Boot verification./stack-verify-boot.shOverall stack readiness judgment
Diagnostic bundle./doctor.shtarball for support requests. Secret values are masked
Change password./passwd.sh --listList of keys that can be changed
# 운영 중 빠른 점검 루틴
cd /opt/kopens/plantpulse-platform-docker/bin
./status.sh
./ops-check.sh

# 문제가 의심되면 진단 번들 생성
./doctor.sh
Old Names Still Work

stack-run.sh · stack-stop.sh · stack-bash.sh · stack-update.sh · stack-remove.sh have not been removed. Running them directly simply prints the new name in one line, but the behavior is the same. You don't need to update existing customer runbooks all at once.

Caution — Deletes All Volumes: ./tools/remove-all-volumes.sh permanently deletes all data. Please use it only after a backup has been completed.

Air-gapped Network Installation

For environments without internet access, install in three steps: obtain the bundle → transfer via media → load on the air-gapped network server.

Step 1: Obtain the Bundle

The recommended path is to download the bundle published by KOPENS.

https://product.kopens.io/plantpulse-platform/plantpulse-platform-images-<version>.tar.gz

If you need to build it yourself on a server with internet access, use the following.

cd /opt/kopens/plantpulse-platform-docker/bin
./airgap-bundle.sh

# 옵션
INCLUDE_DATALAKE=1 ./airgap-bundle.sh # datalake 이미지까지 포함
OS_TARGET=both ./airgap-bundle.sh # 대상 서버가 Ubuntu인 경우 deb 패키지도 포함 (기본은 RHEL rpm)

The output is a single plantpulse-platform-images-<version>.tar.gz file, which includes all of the following:

  • Docker Engine offline installation package (rpm / deb)
  • Platform images (output of docker save)
  • repo/ — all installation/operation scripts and compose/

Step 2: Transfer to the Air-gapped Network Server

Deliver the .tar.gz file to the target server via an approved medium such as USB, an internal file server, or scp.

Step 3: Load and Install on the Air-gapped Network Server

sudo -i
mkdir -p /opt/kopens/plantpulse-platform-docker
tar -xzf plantpulse-platform-images-*.tar.gz -C /opt/kopens/plantpulse-platform-docker

cd /opt/kopens/plantpulse-platform-docker/repo/bin
vi env.sh # 3단계 환경 변수 검토와 동일하게 수정
./airgap-load.sh

airgap-load.sh automatically performs the following.

  1. Offline Docker Engine installation (rpm -ivh / dpkg -i) — skipped if already installed
  2. Loading the platform images docker load
  3. Invoking install.sh (SKIP_LOGIN=1 — no registry login needed since images are already local)

After installation, perform boot verification (./up.sh or ./stack-verify-boot.sh) and the operational check (./ops-check.sh) the same way as with manual installation.

Air-gapped Network Updates

Build a bundle containing the new version's images using the same procedure, transfer and load it, then recreate the existing containers with ./update.sh.

Troubleshooting

When the preflight Check Fails

MessageAction
docker is installed but daemon is not ready while SKIP_OS=1Start the Docker daemon first: systemctl start docker. Alternatively, run without SKIP_OS so that install.sh configures Docker for you
Port already in use (WARN)Another service is using that port. Check the process with ss -tlnp | grep :<port> and clear it before installing the Platform
Docker data-root parent path missing (WARN)The /data1 directory does not exist. Mount the data disk at /data1 or create the directory

When the Stack Fails to Start

Since docker compose up -d waits for all depends_on conditions, if even one container fails its health check, the entire command fails, leaving behind just a single line.

dependency failed to start: container plantpulse-server-web is unhealthy

This single line tells you nothing beyond the container name. The operations script automatically prints the container list, status, health probe output, and each log's tail together at this point, so read that output first. To check manually:

cd /opt/kopens/plantpulse-platform-docker/bin

# 어떤 컨테이너가 어떤 상태인가
./status.sh

# 문제가 있는 컨테이너의 로그
./logs.sh --list # 볼 수 있는 서비스 목록
./logs.sh plantpulse-server-web -n 200

# 자원 상황
df -h
docker stats --no-stream

Frequently observed causes:

CauseCheck
Out of memory (OOM)docker inspect <컨테이너> --format '{{.State.OOMKilled}}'. For per-app mem_limit, refer to the Environment Variable Reference
Disk full/data1 free space
Missing required credentialscompose requires passwords via :?. If empty, the stack does not come up halfway — it fails to come up at all — run install.sh first to create the sidecar
DB initialization delayThe first installation takes a long time due to schema creation. If it's progressing without errors in the logs, please wait a bit longer

If you can't identify the cause, generate a diagnostic bundle with ./doctor.sh and attach it to a technical support request. The diagnostic bundle includes the status and logs of all containers.

When TLS Handshakes Only Show Up as TimeoutException

If the name an app uses to connect to the backend isn't in the certificate's SAN list, the error message won't mention the certificate at all — it will just look like a timeout. Suspect this case if you've renamed a container or specified the backend host directly. See Security Management for how to rebake certificates.

Registry Authentication Failure

# unauthorized 오류 시 재로그인
docker login docker.kopens.io
  • If login credentials are missing or expired, request issuance from the KOPENS operations team.
  • An internal firewall may be blocking access to the registry. Ask your network administrator to allow HTTPS access to the docker.kopens.io and product.kopens.io domains.
  • If external access is completely blocked, please use the air-gapped network installation.

Technical Support

If you need assistance during installation or operation, please feel free to reach out anytime: webmaster@kopens.com