Service Operations
The AI stack is a single docker compose. All operational commands live in bin/ of the
installation itself, and they call compose on your behalf.
cd /opt/kopens/plantpulse-ai-docker/bin
Commonly used commands
| What you want to do | Command | Notes |
|---|---|---|
| Bring it up | ./up.sh | Exit code 0 means «ready» |
| Bring it down | ./down.sh | |
| Restart it | ./restart.sh | 0 means it restarted and is ready |
| Check current status | ./status.sh | Summary of compose stack status |
| Run a detailed check | ./health.sh | Detailed per-service report |
| Follow logs | ./logs.sh | |
| Pull new images and deploy | ./update.sh | Pulls and restarts |
up.sh · restart.sh report 0 not when the container has come up, but when it has
become usable. You don't need to add a separate wait loop when automating this
with a script.
When pinpointing what went wrong
./doctor.sh
🔴 doctor.sh changes nothing. It only reads and judges. So you can safely run it
first during an incident — a human decides what to fix.
Among the lines in the env file, the ones blocking startup are shown as «bad», while
old names · duplicate lines that change nothing even if left alone (the pre-startup
check's [warn] — alias · oldname · media-same) are shown separately as «old or redundant
lines». The latter are not the cause of the failure. bin/lint-env.sh prints the command to
clean them up → Environment Variable Configuration
Working with a single container
If you don't need to shake up the whole stack, give the script the service name.
cd /opt/kopens/plantpulse-ai-docker/bin
./status.sh # 무엇이 떠 있나
./restart.sh plantpulse-rag-api # 0 이면 재시작 후 준비됨
./logs.sh plantpulse-lightrag
docker compose by handThe compose file lives under compose/, and the configuration is in /etc/kopens/plantpulse-ai.env. Running
docker compose … bare from the installation directory either can't find the file, or runs
with a different configuration even if it can — with up this can roll back
to the deployed image. The scripts call it specifying both the compose file and the
configuration file explicitly.
Container names and their respective roles are listed in the table in Installation Overview.
If you need to get inside a container:
bin/shell.sh plantpulse-rag-api
Logs
./logs.sh # 스택 전체를 따라 본다
./logs.sh plantpulse-llm # 서비스 하나
Container logs are rotated by Docker (configured in compose). No files pile up on the host that you need to clean up separately.
Containers run as non-root
As of 2026.10, every service in compose explicitly specifies its runtime user
(user:), and the only one that runs as root is plantpulse-dcgm, which reads GPU metrics
(root and SYS_ADMIN are needed to read GPU profiling fields).
| What | Runtime user (uid:gid) |
|---|---|
| Our images — ingress proxy · copilot · admin console · RAG · time series insight · document understanding · vision · digital thread, etc. | 10001:0 |
vLLM (plantpulse-llm · -emb · -rerank, and plantpulse-asr for installations with video · audio transcription enabled) · plantpulse-asr-audio | 10001:0 |
| Both Neo4j instances | 7474:7474 |
| Qdrant | 1000:1000 |
| Phoenix | 65532:65532 |
| Docling | 1001:0 |
plantpulse-dcgm | root (exception) |
With the ingress proxy (plantpulse-proxy) also becoming non-root, the container's internal
ports changed to 8080 · 9443 · 8443. The ports visible on the host — 80 · 443 · 5960 —
remain unchanged →
Service Port Map
Write volumes created as root on older installations cannot be written to by
non-root processes. Docker does not change the ownership of an existing volume, and
write failures are usually either silent (e.g., appearing only on console instead of
a log file) or immediately fatal (Qdrant dies instantly on a root-owned store). That's
why the startup scripts that up.sh · restart.sh · update.sh go through call bin/volumes-nonroot.sh before
bringing compose up.
| Service | What gets fixed | Owner it's set to |
|---|---|---|
plantpulse-copilot-web · plantpulse-timeseries-insight-api | App logs | 10001:0 |
plantpulse-timeseries-insight-server | App logs · per-installation fine-tuned weights | 10001:0 |
plantpulse-lightrag | Index store · source input | 10001:0 |
vLLM set (transcription plantpulse-asr only on installations with it enabled) | Compile cache | 10001:0 |
plantpulse-asr-audio (only on installations with it enabled) | Audio library volume (pi-asr-audio) | 10001:0 |
plantpulse-neo4j · plantpulse-digital-thread-neo4j | Data | 7474:7474 |
plantpulse-qdrant | Vector store | 1000:1000 |
plantpulse-phoenix | Data | 65532:65532 |
plantpulse-proxy | Certificates (pp-proxy-certs) | 10001:0 |
plantpulse-registry (only when the profile is enabled) | Image store | 10001:0 |
It only changes ownership if there's even one file not owned by that service's user, so it does the work once at first, and merely checks on subsequent runs. If it can't fix ownership, it halts startup (because starting with incorrect ownership causes writes to fail silently).
docker compose up by hand skips this stepOn hosts brought up on 2026.09 or earlier, the first startup must be done with
bin/up.sh (or update.sh · restart.sh).
What doesn't come up by default — compose profiles
| Profile | Containers | Use |
|---|---|---|
registry | plantpulse-registry · plantpulse-registry-ui | Local image store for the air-gapped network hub node |
media | plantpulse-asr · plantpulse-asr-audio | Video · audio transcription (including asking by voice). Enabled with a single line, PI_MEDIA_ENABLED=true, in the host env — the operational scripts (up.sh · restart.sh · status.sh · logs.sh …) check that value and attach the profile → Video · Audio Collection |
For media, don't give the profile name directly — enable it with PI_MEDIA_ENABLED instead.
That switch also aligns the values for rag-api · document understanding, as well as
fetching the transcription model. If you write COMPOSE_PROFILES in the host env, the
pre-startup check will bring startup up in a unknown state.
plantpulse-asr-audio is a one-shot service that moves the audio library to a volume and then
exits. «Exited (0)» in status.sh is normal and counts as healthy. If it exits with a
non-zero code, plantpulse-asr will not come up either.
Backups and passwords
| What you want to do | Command | Documentation |
|---|---|---|
| Take a backup | ./backup.sh (what it covers is listed in ./backup.sh --list — logs · cache and the transcription audio library volume are excluded) | Database Management |
| Change passwords · keys | ./passwd.sh | Changing Passwords · API Keys |
| Remove the installation | ./remove.sh | Cannot be undone |
Databases use the platform's
PostgreSQL · Aster TSDB · Redis (Valkey) · RustFS are not started by the AI stack.
They share the platform installation (host of PP_DATALAKE_ADMIN_URL). If these four go down, AI
stops too, so during an incident, first use the
Service Port Map to determine which side the problem is on.
As of 2026.10, those accounts · passwords are fetched from the platform when the container starts. If the platform is unreachable, the container doing the fetch halts with FATAL, and if the password was changed on the platform, simply restarting the affected container resolves it → Environment Variable Configuration
Related Documents
- AI Admin Console — status as seen on screen
- Logs and Monitoring
- Service Port Map