Network and domains¶
PerfShop runs on a single Docker bridge network named perfshop-network, which isolates communications between the 31 services and enables DNS resolution by container name. On the host side, each application service exposes its port through the standard "<host_port>:<container_port>" mapping configured by environment variables.
This page describes:
- The internal Docker network topology
- The complete table of ports exposed to the host
- The inter-service communication conventions
- The external reverse proxy pattern for exposing the stack over HTTPS
Source of truth
The ports, service names and environment variables on this page are extracted from the docker-compose.desktop.yml and docker-compose.build.yml files. The public DNS conventions are derived from the PUBLIC_*_URL variables of .env.example.
Docker network topology¶
flowchart TB
subgraph host["Host (machine or VPS)"]
rp{"External reverse proxy<br/>(optional)<br/>nginx, Caddy, Traefik..."}
subgraph engine["Docker Engine"]
subgraph bridge["perfshop-network (bridge)"]
direction TB
subgraph c["Core"]
DB[("perfshop-db<br/>:3306")]
APP["perfshop-app<br/>:8080 + :9090"]
FE["perfshop-frontend<br/>:80"]
MON["perfshop-monitoring<br/>:3001"]
ADM["perfshop-admin<br/>:80"]
CADM["perfshop-chaos-admin<br/>:80"]
WC["perfshop-welcome<br/>:3011"]
DOC["docs<br/>:8000"]
end
subgraph o["Observability"]
PROM["prometheus<br/>:9090"]
GRAF["grafana<br/>:3000"]
LOKI["perfshop-loki<br/>:3100"]
PT["perfshop-promtail"]
TEMPO["perfshop-tempo<br/>:3200/:4317/:4318"]
PYRO["perfshop-pyroscope<br/>:4040"]
OS["perfshop-opensearch<br/>:9200"]
OSD["perfshop-opensearch-dashboards<br/>:5601"]
VEC["perfshop-vector"]
end
subgraph q["QA"]
JM["perfshop-jmeter"]
JMUI["perfshop-jmeter-ui<br/>:3005"]
SQDB[("perfshop-squash-db<br/>:5432")]
STM["perfshop-testmgmt<br/>:8080"]
ORCH["perfshop-orchestrator"]
SEL["perfshop-selenium<br/>:4444 + :7900"]
TR["perfshop-test-runner<br/>:22"]
FG["perfshop-forgejo<br/>:3000"]
SUI["perfshop-scripts-ui<br/>:3008"]
end
end
end
rp -->|HTTPS terminating| bridge
end
user["Browser"] -->|HTTPS| rp
user -.-|Direct HTTP if no reverse proxy| bridge
Characteristics:
- Standard bridge driver: no
hostormacvlanmode. All containers have a private IP allocated by Docker in the bridge subnet. - Automatic internal DNS: each container is reachable from the others via its
container_name(which matches the Compose service name). Example:perfshop-appcan reach the DB viajdbc:mysql://perfshop-db:3306/perfshop. - No direct cross-host communication: the stack is single-host. No cluster discovery, no service mesh.
- All services share the same network: this is intentional. No internal network segmentation between groups (core / obs / QA) — the separation is logical, not network-level.
Ports exposed to the host¶
All ports are configurable via environment variables listed in the right column. The default values are those of the .env.example file.
| Service | Host port | Container port | Env variable | Usage |
|---|---|---|---|---|
perfshop-db |
19306 | 3306 | DB_PORT |
External MySQL connection (debug) — ⚠️ port 3306 reserved by Hyper-V/WSL2 on Windows |
perfshop-app |
8080 | 8080 | BACKEND_HTTP_PORT |
Main REST API |
perfshop-app |
9090 | 9090 | BACKEND_METRICS_PORT |
Spring Boot Actuator (/actuator/prometheus, /actuator/health, /actuator/heapdump) |
perfshop-frontend |
8090 | 80 | FRONTEND_HTTP_PORT |
React shop (nginx) |
perfshop-monitoring |
3001 | 3001 | MONITORING_HTTP_PORT |
Real-time HTML dashboard |
perfshop-admin |
3004 | 80 | ADMIN_HTTP_PORT |
Products/orders backoffice |
perfshop-chaos-admin |
3003 | 80 | CHAOS_HTTP_PORT |
Chaos control panel |
perfshop-welcome |
3011 | 3011 | (fixed) | Directory page |
docs |
8087 | 8000 | DOCS_HTTP_PORT |
MkDocs documentation |
prometheus |
9091 | 9090 | PROMETHEUS_HTTP_PORT |
Prometheus UI + scrape endpoint |
grafana |
3002 | 3000 | GRAFANA_HTTP_PORT |
Grafana UI |
perfshop-loki |
19100 | 3100 | LOKI_HTTP_PORT |
Loki API (push + query) — ⚠️ port 3100 reserved by Hyper-V/WSL2 on Windows |
perfshop-tempo |
19200 | 3200 | TEMPO_HTTP_PORT |
Tempo HTTP query API — ⚠️ port 3200 reserved by Hyper-V/WSL2 on Windows |
perfshop-tempo |
4317 | 4317 | TEMPO_OTLP_GRPC_PORT |
OTLP gRPC (traces export from the Java agent) |
perfshop-tempo |
4318 | 4318 | TEMPO_OTLP_HTTP_PORT |
OTLP HTTP (alternative) |
perfshop-pyroscope |
4040 | 4040 | PYROSCOPE_HTTP_PORT |
Pyroscope API (push + query) |
perfshop-opensearch |
9201 | 9200 | OPENSEARCH_API_PORT |
OpenSearch REST API |
perfshop-opensearch-dashboards |
5601 | 5601 | OPENSEARCH_HTTP_PORT |
OpenSearch Dashboards UI |
perfshop-jmeter-ui |
3005 | 3005 | JMETER_UI_PORT |
Custom JMeter UI |
perfshop-squash-db |
5433 | 5432 | SQUASH_DB_PORT |
Squash PostgreSQL (debug) |
perfshop-testmgmt |
8088 | 8080 | SQUASH_HTTP_PORT |
Squash TM |
perfshop-selenium |
4444 | 4444 | SELENIUM_HTTP_PORT |
Selenium WebDriver |
perfshop-selenium |
7900 | 7900 | SELENIUM_VNC_PORT |
noVNC (visual debug for Chrome sessions) |
perfshop-forgejo |
3009 | 3000 | FORGEJO_HTTP_PORT |
Forgejo UI + REST API |
perfshop-scripts-ui |
3008 | 3008 | SCRIPTS_UI_PORT |
Scripts UI |
Port conflicts
If one of these default ports is already in use on the host machine (for example 3306 for another MySQL instance), simply override the corresponding variable in the .env. All internal references use Docker DNS names, so these variables only impact the host-side mapping.
Inter-service communication (Docker DNS)¶
Services talk to each other by container name, never by IP. Here are the main communication chains.
Backend → DB and observability¶
flowchart LR
APP["perfshop-app"]
DB[("perfshop-db:3306")]
TEMPO["perfshop-tempo:4317<br/>(OTLP gRPC)"]
PYRO["perfshop-pyroscope:4040<br/>(JFR push)"]
APP -->|jdbc:mysql://perfshop-db:3306/perfshop| DB
APP -->|OTLP gRPC| TEMPO
APP -->|JFR HTTP push| PYRO
Prometheus → scraped targets¶
flowchart LR
PROM["prometheus"]
APP["perfshop-app:9090<br/>/actuator/prometheus"]
MON["perfshop-monitoring:3001<br/>/metrics"]
JM["perfshop-jmeter:9270<br/>(active during a test run)"]
PROM -->|scrape 5s| APP
PROM -->|scrape 5s| MON
PROM -->|scrape 5s| JM
Grafana → datasources¶
flowchart LR
GRAF["grafana"]
PROM["http://prometheus:9090"]
LOKI["http://perfshop-loki:3100"]
TEMPO["http://perfshop-tempo:3200"]
PYRO["http://perfshop-pyroscope:4040"]
GRAF --> PROM
GRAF --> LOKI
GRAF --> TEMPO
GRAF --> PYRO
Frontend / admin / chaos-admin → backend¶
All HTML/JS UIs call the backend from the browser (cross-domain origins), so:
- The URL called is the public URL of the backend (
PUBLIC_API_URLvariable, e.g.http://localhost:8080orhttps://perfshop-api.example.com). - The backend allows these origins via
CORS_ALLOWED_ORIGINS, computed dynamically from thePUBLIC_*_URLvariables. - The frontend nginx does not act as a proxy to the backend: it only serves static files. API calls go directly from the browser to
perfshop-app.
flowchart LR
B["Student<br/>browser"]
FE["perfshop-frontend<br/>(static HTML/JS)"]
BE["perfshop-app<br/>(REST API)"]
B -->|GET /index.html| FE
FE -->|loads JS bundle| B
B -->|fetch '/api/products'| BE
style FE fill:#fff,stroke:#888
style BE fill:#fff,stroke:#888
QA stack — control chains¶
flowchart LR
SUI["perfshop-scripts-ui"]
FG["perfshop-forgejo:3000"]
TR["perfshop-test-runner:22<br/>(SSH)"]
SEL["perfshop-selenium:4444"]
STM["perfshop-testmgmt:8080"]
ORCH["perfshop-orchestrator"]
SUI -->|Git push API| FG
SUI -->|docker exec via socket| TR
TR -->|WebDriver| SEL
STM -->|SCM polling| FG
ORCH -->|REST| STM
ORCH -->|SSH with public key| TR
TR -->|WebDriver| SEL
JMeter UI → JMeter container¶
perfshop-jmeter-ui does not use DNS resolution to drive JMeter: it mounts the Docker socket (/var/run/docker.sock) and runs docker exec perfshop-jmeter .... This is an out-of-network control channel that lets test runs be launched without having to expose a JMeter control server.
flowchart LR
JMUI["perfshop-jmeter-ui<br/>(node.js)"]
SOCK["/var/run/docker.sock"]
JM["perfshop-jmeter<br/>(idle, tail -f /dev/null)"]
JMUI -->|mounts| SOCK
SOCK -.->|docker exec<br/>jmeter -n -t ...| JM
External reverse proxy (public HTTPS)¶
To expose PerfShop publicly (hosted training, customer demo), an external reverse proxy is placed in front of the stack. PerfShop does not ship this reverse proxy: it is up to the operator to install and configure nginx, Caddy, Traefik, HAProxy, or any other.
Recommended pattern¶
flowchart LR
internet["Students<br/>(browser)"]
rp["Reverse proxy<br/>(nginx / Caddy / Traefik)<br/>:443 HTTPS"]
net["perfshop-network<br/>(local HTTP ports)"]
internet -->|"shop.example.com"| rp
internet -->|"api.example.com"| rp
internet -->|"grafana.example.com"| rp
rp -->|"http://127.0.0.1:8090"| net
rp -->|"http://127.0.0.1:8080"| net
rp -->|"http://127.0.0.1:3002"| net
Subdomain conventions¶
If you want to follow a consistent convention, the naming used by the PUBLIC_*_URL variables of .env.example proposes:
| Variable | Typical subdomain | Internal target |
|---|---|---|
PUBLIC_FRONTEND_URL |
shop.<domain> |
perfshop-frontend:80 (host port 8090) |
PUBLIC_API_URL |
api.<domain> |
perfshop-app:8080 (host port 8080) |
PUBLIC_MONITORING_URL |
monitoring.<domain> |
perfshop-monitoring:3001 |
PUBLIC_GRAFANA_URL |
grafana.<domain> |
grafana:3000 (host port 3002) |
PUBLIC_CHAOS_URL |
chaos.<domain> |
perfshop-chaos-admin:80 (host port 3003) |
PUBLIC_ADMIN_URL |
admin.<domain> |
perfshop-admin:80 (host port 3004) |
PUBLIC_DOCS_URL |
docs.<domain> |
docs:8000 (host port 8087) |
PUBLIC_JMETER_URL |
jmeter.<domain> |
perfshop-jmeter-ui:3005 |
PUBLIC_SCRIPTS_URL |
scripts.<domain> |
perfshop-scripts-ui:3008 |
PUBLIC_FORGEJO_URL |
git.<domain> |
perfshop-forgejo:3000 (host port 3009) |
PUBLIC_OPENSEARCH_URL |
logs.<domain> |
perfshop-opensearch-dashboards:5601 |
PUBLIC_SQUASH_URL |
squash.<domain> |
perfshop-testmgmt:8080 (host port 8088) |
PUBLIC_SELENIUM_VNC_URL |
vnc.<domain> |
perfshop-selenium:7900 |
WebSockets and SSE
Several services use WebSockets or Server-Sent Events (Squash TM, noVNC, OpenSearch Dashboards, Forgejo when editing files). The reverse proxy must explicitly support Upgrade: websocket and disable buffering for these routes.
Propagation to the services¶
Once the PUBLIC_*_URL are set in the .env, they are propagated automatically:
- Backend:
CORS_ALLOWED_ORIGINSis composed fromPUBLIC_FRONTEND_URL,PUBLIC_MONITORING_URL,PUBLIC_CHAOS_URL,PUBLIC_ADMIN_URL. That's what allows the frontend hosted onshop.example.comto callapi.example.com. - React frontend:
VITE_API_URL,VITE_MONITORING_URLare substituted into the bundled JS files at nginx container startup via theenv-inject.shscript. - Monitoring Node: receives all
PUBLIC_*_URLand injects them intowindow.__CONFIG__on the browser side (seemonitoring/src/server.js). - Welcome page: all
PUBLIC_*_URLare consumed to generate the directory of links. - Grafana:
GF_SERVER_ROOT_URLis set toPUBLIC_GRAFANA_URLso absolute links in emails and the API are correct.
Internal and external addresses — recap¶
| Context | URL used | Example |
|---|---|---|
| Container → container (internal to the bridge) | http://<service>:<container_port> |
http://perfshop-app:8080, http://perfshop-loki:3100 |
| Browser → service (without reverse proxy) | http://<host>:<host_port> |
http://localhost:8080, http://192.168.1.10:3002 |
| Browser → service (with reverse proxy) | https://<subdomain>.<domain> |
https://api.example.com, https://grafana.example.com |
| Backend → other backend | always container name | jdbc:mysql://perfshop-db:3306/perfshop |
| Java agent → Tempo / Pyroscope | container name | http://perfshop-tempo:4317, http://perfshop-pyroscope:4040 |
It is the explicit separation between public URLs and internal URLs that makes the stack portable: the same configuration can be migrated between Docker Desktop, a Linux VPS and any external reverse proxy without touching the application code, simply by adjusting the PUBLIC_*_URL variables.
To go further¶
- Deployment topology — Docker Desktop and Linux/VPS modes
- Docker Compose — details of each service and its volumes
- Authentication — CORS mechanism and session cookie policy