Skip to main content

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.

The app container does not publish 80

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.

HostBackend (container name:port)What it is
Everything else (default)plantpulse-copilot-web:8080AI Chat Web — chat UI
admin.*plantpulse-admin-web:8080Admin console
llm.*plantpulse-litellm:4000LLM gateway (OpenAI compatible)
rag.*plantpulse-rag-api:7115RAG search API
timeseries.*plantpulse-timeseries-insight-api:8970Time series insight API
parser.*plantpulse-document-understanding-api:7120Document parsing API
vision.*plantpulse-vision-api:7130Vision 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.

PathBackendWhat 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 401 without X-API-Key: <PI_INBOUND_API_KEY> (or Bearer). /llm/ uses the gateway's master key or a virtual key.
  • Each backend's metrics /metrics is 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
The LiteLLM admin UI does not open via /llm/ui

Because 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.

The default host is already reachable from outside

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 copilot

The 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.

Check who terminates TLS

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.

AddressReceiverKey (default)
http://<AI_SERVER_IP>:5959Direct to plantpulse-admin-web:8080 (plaintext)PI_ADMIN_WEB_PORT (5959)
https://<AI_SERVER_IP>:5960Proxy terminates TLS and forwards to plantpulse-admin-web:8080PI_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.
The 9080 of 2026.09 and earlier is gone

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 proxyWhat it is
808080HTTP (plaintext)
4439443HTTPS — name-based routing for the chat UI, admin.*, etc.
PI_ADMIN_WEB_TLS_PORT (5960)8443Admin 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.

PortServiceWhy it is open
80 / 443plantpulse-proxyThe only user-facing entry point
4000LiteLLMDirect access to the LLM gateway (also reachable via llm.*)
7115RAG APIFor direct access by MCP clients (Claude Desktop, etc.)
8970Time series insight APIFor direct MCP client access
5959Admin console (plantpulse-admin-web) — HTTPFor operators. Also reachable via admin.*
5960Admin console — HTTPS (received by plantpulse-proxy)For operators. Same certificate as 443
7474Neo4j BrowserGraph DB admin UI (plantpulse-neo4j — RAG knowledge graph)
7687Neo4j BoltGraph DB query protocol (same instance)
There are two Neo4j instances — the digital thread one is not exposed

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

Port 4000 has an admin UI that people log into

: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.

PortServiceProfile
5000Local image registryregistry
LOCAL_REGISTRY_UI_PORT (default 5080)Registry admin UIregistry
(not published — 8000 inside)Transcription vLLM plantpulse-asrmedia
(no network)Transcription audio library plantpulse-asr-audio — one-shotmedia

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

Phoenix comes up but is not exposed on the host

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
PortService
9081Admin console backend (admin-api)
8000 · 8003 · 8004vLLM generation · embedding · reranking
8005Transcription vLLM (only when the media profile is enabled)
5001docling
8980Time series inference server
9400DCGM Exporter
6333Qdrant
6006Phoenix
These ports have no authentication

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.

ServiceInternal address (container name:port)Role
AI Chat Webplantpulse-copilot-web:8080Chat UI (the proxy's default backend)
Admin console backendplantpulse-admin-apiAuthentication system of record. plantpulse-admin-web's nginx forwards to it
Document parsing APIplantpulse-document-understanding-api:7120parser.* backend
Vision APIplantpulse-vision-api:7130vision.* backend
LightRAGplantpulse-lightrag:9621RAG engine. Document management WebUI (/webui)
Qdrantplantpulse-qdrant:6333Vector DB
Doclingplantpulse-docling-serve:5001Document conversion
vLLM (generation)plantpulse-llm:8000Local LLM inference
vLLM (embedding)plantpulse-emb:8000Embedding
vLLM (rerank)plantpulse-rerank:8000Reranking
Time series inferenceplantpulse-timeseries-insight-server:8980Insight model serving
Transcription vLLMplantpulse-asr:8000Speech → text. Comes up only with the media profile
DCGM Exporterplantpulse-dcgm:9400GPU metrics
Phoenixplantpulse-phoenix:6006Trace UI · OTLP collection (starts by default, not published)
Digital thread APIplantpulse-digital-thread-api:7140Connector registry · factory graph mirror · thread_ tools for chat. Called by the admin console and the MCP Gateway
Digital thread Neo4jplantpulse-digital-thread-neo4j:7474Factory graph mirror store. Only the digital thread API connects to it
Engineering calculation APIplantpulse-engcalc-api:7150eng_ tools for chat (MCP /mcp/). Called only by the MCP Gateway
MCP Gatewayplantpulse-mcp-gateway-api:7160A 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))
Two legacy services have been archived

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​

TargetPorts to open
All users80 / 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.