Docker Installation
Overview
This page provides a guide on how to perform a manual (step-by-step) installation of the PlantPulse Platform as a Docker Compose stack.
For general environments, the one-line installation is recommended. The one-line installation handles everything from package download to automatic environment detection, installation, and boot verification in one go. Please use the manual procedures on this page only in the following cases:
- When each installation step must be directly reviewed and approved (e.g., security audits, change management procedures).
- When environment variables (
bin/env.sh) must be manually specified instead of using automatically detected values. - When installing in an air-gapped network environment where internet access is blocked → Air-gapped Installation.
Installation Structure — Nine Containers
The Platform operates as a single docker compose stack. The master copy is compose/docker-compose.yml, and all operational scripts interact with the containers through this file.
| Container | Layer | Role |
|---|---|---|
plantpulse-certs | Certificate | Bakes TLS materials and stays resident to monitor expiration. Healthy when (healthy) |
plantpulse-datalake | Infrastructure | Storage, messaging, analytics, CEP, SQL, monitor |
plantpulse-server-web | Application | Web console |
plantpulse-batch-web | Application | Batch |
plantpulse-warehouse-web | Application | Data warehouse |
plantpulse-plugin-opcua | Application | OPC-UA server plugin (11004 / 11005) |
plantpulse-plugin-aasx | Application | AASX server plugin |
plantpulse-ha | Application | Redundancy recovery daemon (10210) |
plantpulse-proxy | Edge | The sole entry point for users (80 / 443 / 1883 / 1884) |
It is normal if all nine appear as (healthy) in docker ps.
Additionally, plantpulse-mirror-maker for mirroring is defined in the compose file, but it is tied to the mirror profile and does not start by default.
Prior to 2026-08-29, a «monolith» configuration was available where all components ran together in a single plantpulse-platform container. This configuration has been deprecated, and the selection variable (PP_TOPOLOGY) and the corresponding compose file have been deleted. There is no longer a choice — bin/up.sh launches the above stack.
Commands such as docker logs plantpulse-platform found in old runbooks will not work as containers with those names no longer exist. Replacement commands can be found in the Operation Command Summary.
The primary purpose of separating containers by app is OOM isolation. If one app exhausts its memory, other apps and the infrastructure remain operational, and restarts or rollbacks can be performed at the app level.
Prerequisites
Please verify the following before starting the installation.
| Item | Requirement |
|---|---|
| Account Privileges | root (or sudo privileges). The installation script configures OS settings and the Docker daemon |
| Operating System | RHEL · Rocky Linux · Oracle Linux 8.x · 9.x · 10.x, Ubuntu 20.04+, Amazon Linux 2/2023 |
| Docker | Compose v2 is required (docker compose — the form without a hyphen). Install if install.sh is missing |
| Data Disk | A large-capacity disk mount is recommended at the /data1 path — Docker data (/data1/docker-data) and Platform data volumes use this path |
| Registry Access | HTTPS access must be possible to docker.kopens.io (image registry) and product.kopens.io (installation package). For environments where access is blocked, please use Air-gapped Installation |
| Registry Credentials | docker.kopens.io login credentials (issued by the KOPENS operations team) |
Disk Path Guide: If a separate data disk is not available, the system will work by creating a
/data1directory on the root disk, but it is recommended to mount a dedicated disk to/data1in production environments.
Manual Installation Procedure
Step 1: Download Installation Package
Download the installation package (tar.gz) from the download server and extract it to the standard path. Git cloning or installation of separate tools is not required.
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
Two locations within the package are actually used:
| Location | Contents |
|---|---|
bin/ | All installation and operation scripts |
compose/ | Master stack docker-compose.yml and cluster worker overlay |
Step 2: Preflight Inspection
preflight.sh checks installation feasibility without making any changes to the system.
./preflight.sh
Inspection items:
- Docker installation/daemon operation status (it is okay if not installed —
install.shwill install it) - Existence of the Docker data-root parent path (
/data1) - Existence of the secret sidecar (
/etc/kopens/plantpulse-platform.env) - Usage status of major operational ports (80, 443, 4949, 4950)
Proceed to the next step only if OK appears. If ERROR is present, please refer to Troubleshooting. (WARN is for reference and does not block installation.)
Step 3: Review Environment Variables (env.sh)
bin/env.sh determines the default values for the host side, and values changed by the operator are written to the sidecar /etc/kopens/plantpulse-platform.env. The values actually seen by the containers are determined by compose/docker-compose.yml — for detailed relationships, refer to the Environment Variable Reference.
vi /etc/kopens/plantpulse-platform.env
Values written in bin/env.sh will be lost during reinstallation or upgrades. The installation script deletes and redownloads the installation tree.
env.sh automatically determines CPU, memory, and disk by looking at the host. Since these are not fixed defaults, appropriate smaller values are assigned to smaller boxes without manual intervention. The value specified per box always takes precedence.
| Variable | How it is determined | When to modify |
|---|---|---|
DOCKER_PP_CPUS | nproc (if read fails, 8) | When wanting to assign fewer cores to the container |
DOCKER_PP_CLUSTER_CORES | DOCKER_PP_CPUS - 2, min 4 · max 30 | Usually left as is |
DOCKER_PP_MEMORY | 90% of host RAM, min 8G | When wanting to leave more for the OS |
DOCKER_DATALAKE_MEMORY | 96G (minimum). Truncated to not exceed 90% of host RAM — Hosts under 107G are insufficient | Only when increasing. Do not lower below 96G |
DOCKER_PP_DATA_DISK_NAME | Automatic reverse tracking of the actual disk supporting / (if failed, sda) | When automatic detection is incorrect |
PP_LANG | en | Use ko for Korean operations |
PP_TZ | Asia/Seoul | Overseas boxes |
DOCKER_PP_EXTERNAL_IP | Empty value | Only when the external public IP must be specified in a NAT environment |
DOCKER_PP_EXTERNAL_IPThis value passes through PP_SERVICE_IP and enters the SAN list of the TLS certificate. If even one incorrectly formatted IP is mixed in, openssl will reject the entire extension file, resulting in no certificates being generated, and the stack will fail to start. If not behind a NAT, leave it empty — that is the default.
Current server values can be verified with the following command:
hostname -I | awk '{print $1}' # 서버 IP
free -g | awk '/^Mem:/{print $2"G"}' # 전체 메모리
nproc # CPU 코어 수
lsblk # 디스크 이름 (sda, sdb, nvme0n1 …)
In Korean operational boxes, typically only two lines are modified:
export PP_LANG=ko
export PP_TZ=Asia/Seoul
PP_LANG / PP_TZ are fixed at JVM boot time. To change them in a running stack, ./restart.sh is required. Additionally, since Aster TSDB time series are loaded as KST epoch, please maintain Asia/Seoul for Korean operations.
Step 4: Execute Installation (install.sh)
sudo ./install.sh
A single install.sh automatically performs the entire installation process:
- OS Detection and System Configuration — File limits, kernel parameters (sysctl), time synchronization (chrony), SELinux permissive transition, swap disablement.
- Docker Engine Installation — Installed via OS-specific package manager + data-root configured to
/data1/docker-data(Compose v2 installed if missing). - Firewall Configuration — Opening public ports (80/443/4949/4950) and restricting internal ports to the private network range.
- Service Credential Generation — Creation of the
/etc/kopens/plantpulse-platform.envsecret sidecar (permission 0600, does not overwrite existing values on rerun). - Registry Login — Input of
docker.kopens.iocredentials (automatically skipped if already logged in). - Stack Launch — Network/Volume creation → Image pull → Configuration template seeding →
docker compose up -d.
One-time options during installation (specify via environment variables only if necessary):
| Option | Effect |
|---|---|
SKIP_OS=1 | Skip OS configuration and Docker installation (if Docker is already installed and running) |
SKIP_LOGIN=1 | Skip registry login (if already logged in or images are local) |
SKIP_FW=1 | Skip firewall configuration (if firewall is managed separately) |
DOCKER_DATA_DIR=<path> | Change Docker data-root (default /data1/docker-data) |
# 예: Docker가 이미 설치된 서버
SKIP_OS=1 sudo -E ./install.sh
The secret sidecar (/etc/kopens/plantpulse-platform.env) uses a mandatory assignment of the form export VAR=값, which overrides shell exports. On a node where installation is complete, PP_PG_PASSWORD=... ./up.sh is quietly ignored. For replacement during delivery, use export PP_*_PASSWORD=... before installation; after installation, use passwd.sh in Password Rotation.
Step 5: Launch and Boot Verification
install.sh launches the stack and returns. Boot completion must be verified separately.
./up.sh
up.sh is idempotent and safe to run even if the stack is already up, and it waits until the system is ready. Exit code 0 does not mean «the command succeeded» but rather «it is now ready for use».
| Environment Variable | Default Value | Meaning |
|---|---|---|
PP_READY_TIMEOUT | 900 | Readiness wait upper limit (seconds) |
PP_READY_INTERVAL | 15 | Verification interval (seconds) |
PP_WAIT=0 | — | Do not wait. In this case, 0 does not signify readiness |
The process launch itself takes 3-5 minutes (JVM warm-up), but it takes 15-18 minutes for all components to stabilize. Aster TSDB schema migration and stabilization are the slowest. Restarts are much faster as the schema already exists.
Measured criteria (2026-08-31, 32 vCPU / 128GiB): Data Lake 217s, Web Server 316s.
You can also run the readiness determination manually:
./stack-verify-boot.sh
This script monitors the entire stack, not just a single container — it verifies if all nine are running/healthy and if the proxy is serving port 443.
Step 6: Status and Operational Inspection
./status.sh # 0 = 정상 / 2 = 비정상
./ops-check.sh
status.sh summarizes the service list, container status, health, and volumes. The exit code is the contract — it can be used directly in automation.
| Determination | What is considered abnormal |
|---|---|
| Unable to read service list | Compose parsing failure or Docker access unavailable |
| Container missing for a service declared in compose | Not started |
Container is not running | All nine must operate continuously — no exceptions |
Health of a resident container is unhealthy | starting / none are pending |
| Health API is not OK | Probe inside Data Lake |
Volumes and configurations are reported only and are not included in the exit code — since it is normal for some to be missing depending on the installation configuration.
ops-check.sh additionally scans for recent critical logs (OOM, FATAL, SSL errors, etc.).
Health Verification
The monitor API is served by the plantpulse-datalake container. The command differs depending on where you are querying 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
It is normal if "status" is OK or WARN, and it is a failure if it is FAIL.
The console and health API are served on both ports — 4950 (HTTPS) and 4949 (plain HTTP). They are the same console and API, differing only in scheme. 4949 no longer redirects to 4950.
4949 is plain text — login passwords and session cookies flow as is. Use 4950 in untrusted networks. 4949 is an option for boxes where self-signed certificate warnings would actually obstruct the operator.
Installation Deliverables
Networks and Volumes
| Resource | Name | Purpose |
|---|---|---|
| Network | pp-net | Platform-dedicated Docker network (default 10.99.0.0/24, gateway 10.99.0.1) |
| Volume | pp-data | Persistent data storage (Aster TSDB, PostgreSQL, Kafka, etc.) |
| Volume | pp-temp | Temporary data (Spark, Hive workspace) |
| Volume | pp-backup | Backup storage |
| Volume | pp-security | TLS certificates/keystores. Written by plantpulse-certs, read-only for others |
| Volume | pp-proxy-certs | Certificates used by proxy on 443 |
Volumes are always preserved even if containers are stopped or removed, ensuring data is maintained during updates or reinstalls.
Host Directories
| Path | Purpose |
|---|---|
/opt/kopens/plantpulse-platform-docker | Installation/operation scripts and master compose file |
/etc/kopens/conf | Platform configuration templates (host bind mount). Automatically seeded from the image upon first run; subsequently editable by the operator on the host and preserved during reinstallation |
/etc/kopens/plantpulse-platform.env | Service credential secret sidecar (permission 0600) |
/etc/kopens/platform.node.env | Node identity (values unique to that box, such as IP/disk). Do not copy between nodes |
/etc/kopens/ca | Shared cluster CA (used in 2-node split installation) |
Access Addresses
| Purpose | Address |
|---|---|
| Web Console | http://<server-ip>/ · https://<server-ip>/ — received by plantpulse-proxy |
| Admin UI | https://<server-ip> (same address as web console) |
| Monitor UI · Health | https://<server-ip>:4950/api/health |
| MQTT | <server-ip>:1883 (plain) · <server-ip>:1884 (TLS) |
| OPC-UA | <server-ip>:11004 · <server-ip>:11005 |
Security Guide: After the first login to the web console, be sure to change the default administrator password. → Initial Password
Operation Command Summary
Commands used for daily operations. All are executed in /opt/kopens/plantpulse-platform-docker/bin/. All verbs support --help.
| Task | Command | Note |
|---|---|---|
| Status Inspection | ./status.sh | Service / health / volume summary. 0=Normal / 2=Abnormal |
| Operational Inspection | ./ops-check.sh | Container health + Health API + recent critical logs |
| Launch | ./up.sh | Idempotent. 0 = Ready |
| Stop | ./down.sh | Preserve state |
| Restart | ./restart.sh | Graceful drain → Stop → Launch → Wait for readiness |
| View Logs | ./logs.sh [서비스] | Prints last 200 lines and ends. Use -f to follow. All containers if no argument. List via --list |
| Enter Container | ./shell.sh [서비스] | Data Lake if no argument |
| Update Stack | Rerun installation one-liner | Not ./update.sh — see Upgrade |
| Full Removal | ./remove.sh | Remove containers only (preserve volumes/configs). RM_IMAGE=1 / RM_NETWORK=1 |
| Backup | ./backup.sh [볼륨 …] | Default pp-data · pp-security |
| Boot Verification | ./stack-verify-boot.sh | Full stack readiness determination |
| Diagnosis Bundle | ./doctor.sh | Tarball for support requests. Secret values are masked |
| Change Password | ./passwd.sh --list | List of changeable keys |
# 운영 중 빠른 점검 루틴
cd /opt/kopens/plantpulse-platform-docker/bin
./status.sh
./ops-check.sh
# 문제가 의심되면 진단 번들 생성
./doctor.sh
stack-run.sh · stack-stop.sh · stack-bash.sh · stack-update.sh · stack-remove.sh have not been deleted. Executing them simply provides a one-line guide to the new name, but the operation remains the same. Existing customer runbooks do not need to be updated all at once.
Caution — Total Volume Deletion:
./tools/remove-all-volumes.shpermanently deletes all data. Use only after completing a backup.
Air-gapped Installation
Air-gapped installation has been moved to Air-gapped Installation. Information on bundle contents, environment variables for airgap-bundle.sh, and what airgap-load.sh blocks before installation is located there.
Troubleshooting
If preflight inspection fails
| Message | Action |
|---|---|
docker is installed but daemon is not ready while SKIP_OS=1 | Please start the Docker daemon first: systemctl start docker. Or run without SKIP_OS and install.sh will configure Docker |
| Port already in use (WARN) | Another service is using the port. Verify the process with ss -tlnp | grep :<port> and clear it before platform installation |
| Docker data-root parent path missing (WARN) | /data1 directory is missing. Mount the data disk to /data1 or create the directory |
If the stack does not start
docker compose up -d waits for all depends_on conditions; if a single container fails the health check, the entire command fails, leaving only one line.
dependency failed to start: container plantpulse-server-web is unhealthy
This one line tells you nothing other than the container name. The operational script automatically prints the container list, status, health probe output, and log tails at this time, so please 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
Commonly verified causes:
| Cause | Verification |
|---|---|
| Out of Memory (OOM) | docker inspect <컨테이너> --format '{{.State.OOMKilled}}'. Refer to Environment Variable Reference for app-specific mem_limit |
| Disk Full | Free space via /data1 |
| Missing Required Credentials | Compose requires passwords as :?. If empty, the stack will not even partially start — run install.sh first to create the sidecar |
| DB Initialization Delay | First installations take time due to schema creation. If proceeding without errors in the logs, please wait longer |
If the cause is difficult to identify, create a diagnosis bundle using ./doctor.sh and attach it to your technical support request. The diagnosis bundle contains the status and logs of all containers.
If TLS handshake only appears as TimeoutException
If the name used by an app to connect to the backend is not in the certificate SAN list, the error message will not mention certificates at all and will look like a timeout. If you have changed container names or manually specified a backend host, suspect this case. The method to rebake certificates is in Security Management.
Registry Authentication Failure
# unauthorized 오류 시 재로그인
docker login docker.kopens.io
- If login credentials are missing or expired, request issuance from the KOPENS operations team.
- Internal firewalls may block registry access. Request HTTPS access allowance for
docker.kopens.ioandproduct.kopens.iodomains from your network administrator. - For environments where external access is fundamentally blocked, please use Air-gapped Installation.
Technical Support
If you need assistance during installation or operation, please contact us at any time: webmaster@kopens.com