Service Port Map
Only one thing is exposed externally
In the AI stack, the only thing that claims the host's 80 / 443 is plantpulse-proxy (nginx).
The chat UI, the admin console, and every API all come in through this proxy. The admin console's HTTPS
port (5960 by default) is also received by this proxy — see below.
AI Chat Web is open only inside the container (expose: 8080). The only place that faces outward is the proxy
alone — exposure is concentrated in one spot so that firewalling, auditing, and TLS are all handled there.
Routing is split by host name
The proxy looks at the Host header to pick the backend. So you must point the names below at the proxy IP
in DNS for each service to be reachable.
The backends are exactly the container names — the values below are the strings that go into nginx's set $upstream,
and the same names are used when you curl from inside a container.
| Host | Backend (container name:port) | What it is |
|---|---|---|
| Everything else (default) | plantpulse-copilot-web:8080 | AI Chat Web — chat UI |
admin.* | plantpulse-admin-web:8080 | Admin console |
llm.* | plantpulse-litellm:4000 | LLM gateway (OpenAI compatible) |
rag.* | plantpulse-rag-api:7115 | RAG search API |
timeseries.* | plantpulse-timeseries-insight-api:8970 | Time series insight API |
parser.* | plantpulse-document-understanding-api:7120 | Document parsing API |
vision.* | plantpulse-vision-api:7130 | Vision API |
Paths on the default host — when you run with a single domain
So that you do not have to create more subdomains, the paths below are also open on the default host (the chat UI side). One domain and one certificate get you to the gateway and the MCP servers.
| Path | Backend | What it is |
|---|---|---|
/mcp/gateway/ | plantpulse-mcp-gateway-api:7160/mcp/ | MCP Gateway — the address external MCP clients connect to (gateway key ppgw_…). The management REST /api/v1 is not exposed |
/llm/ | plantpulse-litellm:4000/ | LLM gateway (OpenAI compatible) — base_url = "https://<호스트>/llm/v1" |
/mcp/rag/ | plantpulse-rag-api:7115/mcp/ | RAG MCP server |
/mcp/timeseries/ | plantpulse-timeseries-insight-api:8970/mcp/ | Time series insight MCP server |
/mcp/parser/ | plantpulse-document-understanding-api:7120/mcp/ | Document parsing MCP server |
/mcp/vision/ | plantpulse-vision-api:7130/mcp/ | Vision MCP server |
- Include the trailing slash. If a request arrives without it, it is redirected to
308(the body is preserved). - The only MCP transport is Streamable HTTP. There is no
/sse. - Authentication is done by the backend, not the proxy. The four MCP endpoints return
401withoutX-API-Key: <PI_INBOUND_API_KEY>(or Bearer)./llm/uses the gateway's master key or a virtual key. - Each backend's metrics
/metricsis not a new port but a path on the same port, and it too sits behind the inbound key (2026.10 re-release) → Logging and Monitoring
/llm/uiBecause the path prefix is stripped before forwarding, the admin UI's asset links break. Open the admin UI on the
llm.* host or on the directly published :4000/ui.
These paths are directly reachable via the name published to the internet. If PI_INBOUND_API_KEY is empty the pre-start check
stops the stack, but the CHANGE_ME_… placeholder passes that check. Right after installation, be sure to verify with
bin/doctor.sh that no placeholders remain.
copliot is the repository name; the container is copilotThe repository is plantpulse-ai-copliot-web (the typo stuck), but the container name is plantpulse-copilot-web. If you type docker logs with the
repository spelling, it will not be found.
The proxy also listens on 443, but in deployments that put a separate load balancer or reverse proxy in front,
TLS is terminated there and traffic reaches the proxy in plaintext. Configuring it to ignore the upstream
termination causes a redirect loop. Confirm which form you have first.
The admin console is also open by port
Internal users who connect by IP have no host name, so they cannot select the admin console on 443. For that reason, from 2026.10 the admin console has two entry points separated by port.
| Address | Receiver | Key (default) |
|---|---|---|
http://<AI_SERVER_IP>:5959 | Direct to plantpulse-admin-web:8080 (plaintext) | PI_ADMIN_WEB_PORT (5959) |
https://<AI_SERVER_IP>:5960 | Proxy terminates TLS and forwards to plantpulse-admin-web:8080 | PI_ADMIN_WEB_TLS_PORT (5960) |
- Both are open. The plaintext entry point is not redirected to HTTPS.
- The HTTPS certificate is the same one used on 443 (the proxy's certificate). The console does not hold its own.
- To change the ports, edit the two keys above in the host env (
/etc/kopens/plantpulse-ai.env) and bring the stack back up.
Older releases published the admin console on :9080. In 2026.10 it changed to 5959 (HTTP) · 5960 (HTTPS), and
because the console container is also a non-root nginx, its internal port changed from 80 to 8080. Update your
bookmarks and firewall rules accordingly.
The proxy is non-root too — outer ports unchanged, only inner ports changed
In 2026.10, plantpulse-proxy runs as uid 10001, gid 0 rather than root, and inside the container it does not open
ports below 1024. The ports visible on the host (outside) have not changed.
| Host (outside) | Inside the proxy | What it is |
|---|---|---|
| 80 | 8080 | HTTP (plaintext) |
| 443 | 9443 | HTTPS — name-based routing for the chat UI, admin.*, etc. |
PI_ADMIN_WEB_TLS_PORT (5960) | 8443 | Admin console HTTPS |
Firewalls, upstream load balancers, and DNS can keep looking at 80 · 443 · 5960 as before. The inner ports change together with compose and the proxy image in the same release, so rolling back only the proxy image to an older version will cause connections to be refused — when rolling back, align the whole stack to the same release.
If the certificate volume (pp-proxy-certs) was created back when things ran as root, the key is root-only and the non-root
proxy cannot read it. bin/up.sh · restart.sh · update.sh fix the ownership once before startup →
Service Operations
Ports opened directly on the LAN
These ports are published straight to the host without going through the proxy. They are not bound to 127.0.0.1 but
opened on all interfaces — block them with a firewall so they are not reachable from outside the internal network.
| Port | Service | Why it is open |
|---|---|---|
| 80 / 443 | plantpulse-proxy | The only user-facing entry point |
| 4000 | LiteLLM | Direct access to the LLM gateway (also reachable via llm.*) |
| 7115 | RAG API | For direct access by MCP clients (Claude Desktop, etc.) |
| 8970 | Time series insight API | For direct MCP client access |
| 5959 | Admin console (plantpulse-admin-web) — HTTP | For operators. Also reachable via admin.* |
| 5960 | Admin console — HTTPS (received by plantpulse-proxy) | For operators. Same certificate as 443 |
| 7474 | Neo4j Browser | Graph DB admin UI (plantpulse-neo4j — RAG knowledge graph) |
| 7687 | Neo4j Bolt | Graph DB query protocol (same instance) |
From 2026.10 an additional Neo4j dedicated to the digital thread (plantpulse-digital-thread-neo4j) comes up. This one publishes no ports —
only plantpulse-digital-thread-api connects to it inside the compose network. The 7474 · 7687 above are the pre-existing plantpulse-neo4j (LightRAG's
knowledge graph) →
Digital Thread
:4000/ui is the LiteLLM admin UI, and you sign in with PI_LITELLM_UI_USERNAME · PI_LITELLM_UI_PASSWORD (defaults
admin / admin123!). Since it is open on the LAN, change the password during commissioning →
Changing the Initial Password
Things that only come up when a profile is enabled
These are not part of the default startup; they come up only when you bring the stack up with the corresponding compose profile.
| Port | Service | Profile |
|---|---|---|
| 5000 | Local image registry | registry |
LOCAL_REGISTRY_UI_PORT (default 5080) | Registry admin UI | registry |
| (not published — 8000 inside) | Transcription vLLM plantpulse-asr | media |
| (no network) | Transcription audio library plantpulse-asr-audio — one-shot | media |
registry is used to serve images internally on an air-gapped network → Air-gapped Installation.
media is for video and audio transcription. Rather than passing the profile name by hand, you enable it with a single
PI_MEDIA_ENABLED=true line in the host env — the operations scripts read that value and attach the profile →
Video and Audio Collection
Arize Phoenix (trace UI · OTLP collection, 6006) comes up by default — no profile needed. However, since 2026.09 it does not publish a host port. Phoenix has no authentication, and what it holds is the body of questions and answers (including equipment ids and tags in tool arguments). The admin console's status checks go to the container-internal address, so they are unaffected. If you need it for diagnostics, open it briefly via "Diagnostic ports" below.
Langflow (7860) and Jupyter (3500), dropped in the 2026-07 restructuring, were genuinely removed and cannot be brought back.
Diagnostic ports — PP_DEBUG_PORTS=1
Ports that are normally closed can be opened exactly as before, only while diagnosing (compose/docker-compose.debug.yml overlay).
cd /opt/kopens/plantpulse-ai-docker
PP_DEBUG_PORTS=1 bin/compose-up.sh
| Port | Service |
|---|---|
| 9081 | Admin console backend (admin-api) |
| 8000 · 8003 · 8004 | vLLM generation · embedding · reranking |
| 8005 | Transcription vLLM (only when the media profile is enabled) |
| 5001 | docling |
| 8980 | Time series inference server |
| 9400 | DCGM Exporter |
| 6333 | Qdrant |
| 6006 | Phoenix |
When you are done diagnosing, bring the stack back up without PP_DEBUG_PORTS to close them.
Internal only (not published)
These are reachable only by service name inside the compose network. Getting no response when you poke them from
the host with nc -z is normal.
| Service | Internal address (container name:port) | Role |
|---|---|---|
| AI Chat Web | plantpulse-copilot-web:8080 | Chat UI (the proxy's default backend) |
| Admin console backend | plantpulse-admin-api | Authentication system of record. plantpulse-admin-web's nginx forwards to it |
| Document parsing API | plantpulse-document-understanding-api:7120 | parser.* backend |
| Vision API | plantpulse-vision-api:7130 | vision.* backend |
| LightRAG | plantpulse-lightrag:9621 | RAG engine. Document management WebUI (/webui) |
| Qdrant | plantpulse-qdrant:6333 | Vector DB |
| Docling | plantpulse-docling-serve:5001 | Document conversion |
| vLLM (generation) | plantpulse-llm:8000 | Local LLM inference |
| vLLM (embedding) | plantpulse-emb:8000 | Embedding |
| vLLM (rerank) | plantpulse-rerank:8000 | Reranking |
| Time series inference | plantpulse-timeseries-insight-server:8980 | Insight model serving |
| Transcription vLLM | plantpulse-asr:8000 | Speech → text. Comes up only with the media profile |
| DCGM Exporter | plantpulse-dcgm:9400 | GPU metrics |
| Phoenix | plantpulse-phoenix:6006 | Trace UI · OTLP collection (starts by default, not published) |
| Digital thread API | plantpulse-digital-thread-api:7140 | Connector registry · factory graph mirror · thread_ tools for chat. Called by the admin console and the MCP Gateway |
| Digital thread Neo4j | plantpulse-digital-thread-neo4j:7474 | Factory graph mirror store. Only the digital thread API connects to it |
| Engineering calculation API | plantpulse-engcalc-api:7150 | eng_ tools for chat (MCP /mcp/). Called only by the MCP Gateway |
| MCP Gateway | plantpulse-mcp-gateway-api:7160 | A single address (/mcp/) gathering the platform · built-in · external MCP servers, and the admin REST (/api/v1). Called by the copilot and the admin console. Storage is the gw schema in the platform PG pi (2026.10 re-release (2026-10-20)) |
plantpulse-mcp-server (50000) and plantpulse-ontology (8888) no longer exist as standalone services. MCP and ontology tools are provided by the
platform's server-web integrated MCP (/api/v5/mcp, api_key authentication).
Borrowed from the Platform
PostgreSQL (5432) · Aster TSDB (9042) · Redis/Valkey (6379) · RustFS (9000/9001) are not started by the AI stack. They are shared from the PlantPulse Platform installation. The firewall rules and accounts for these ports follow the Platform's Security Settings.
Checking port status
Checking overall status
cd /opt/kopens/plantpulse-ai-docker/bin
./status.sh
health.sh also probes each service's health endpoint. A container being up and actually responding are two different
things, so use this one when investigating faults.
Checking an individual port
# 특정 포트 리스닝 확인
nc -z 127.0.0.1 <port> && echo "OK" || echo "DOWN"
# 포트 사용 프로세스 확인
ss -tlnp | grep <port>
What to open in the firewall
| Target | Ports to open |
|---|---|
| All users | 80 / 443 (proxy) |
| Operators (internal network) | 5959 · 5960 admin console, 7474 Neo4j Browser |
| MCP clients (internal network) | 7115 RAG, 8970 TimeSeries |
After opening the firewall, the installer (install.sh) prints a warning for any of these ports already held by a process
outside this stack — leave it as is and the first bin/up.sh will bring the stack halfway up and then stop with
«address already in use». Free that port, or change the three movable keys (PI_LITELLM_PORT · PI_ADMIN_WEB_PORT · PI_ADMIN_WEB_TLS_PORT) in the host
env. The remaining ports are fixed in compose.
There is no reason to open any other port, even inside the internal network. Database ports (5432 · 9042 · 6333 6379 · 7687 · 9000) must never be exposed externally under any circumstances.