Docker Compose¶
PerfShop is shipped as a Docker Compose stack made of 31 services (30 documented here, plus a pedagogical games hub described in a dedicated section). They are all attached to the same perfshop-network bridge network and organized into logical groups.
This page is the exhaustive inventory of every service. For each group, a synthetic table comes first, then a detailed sub-section for the services that have specifics worth explaining (critical variables, volumes, dependencies).
Source of truth
The information on this page comes from the docker-compose.desktop.yml (Docker Desktop) and docker-compose.build.yml (Linux/VPS) files, which have strictly the same list of application services. All images, ports, volumes, healthchecks and dependencies are verifiable in these files.
Summary table¶
| Group | Service | Image | Host port (default) | Short role |
|---|---|---|---|---|
| Core | perfshop-db |
mysql:8.0 |
19306 | Main database |
| Core | perfshop-app |
perfshop-backend:v18 |
8080 + 9090 | Spring Boot backend |
| Core | perfshop-frontend |
perfshop-frontend:v17 |
8090 | React shop + nginx |
| Core | perfshop-monitoring |
perfshop-monitoring:v4 |
3001 | Real-time Node dashboard |
| Core | perfshop-admin |
perfshop-admin:v4 |
3004 | Products/orders backoffice |
| Core | perfshop-chaos-admin |
perfshop-chaos-admin:v8 |
3003 | Chaos control panel |
| Core | perfshop-welcome |
perfshop-welcome:v6 |
3011 | Directory landing page |
| Core | docs |
squidfunk/mkdocs-material:latest |
8087 | MkDocs documentation |
| Observability | prometheus |
prom/prometheus:latest |
9091 | Metrics |
| Observability | grafana |
grafana/grafana:12.0.0 |
3002 | Dashboards and exploration |
| Observability | perfshop-grafana-seed |
python:3.11-slim |
— | Grafana init (one-shot) |
| Observability | perfshop-loki |
grafana/loki:latest |
19100 | Log storage |
| Observability | perfshop-promtail |
grafana/promtail:latest |
— | Docker logs collection → Loki |
| Observability | perfshop-tempo |
grafana/tempo:2.4.2 |
19200 + 4317 + 4318 | OTLP traces storage |
| Observability | perfshop-pyroscope |
grafana/pyroscope:latest |
4040 | JVM continuous profiling |
| Observability | perfshop-opensearch |
opensearchproject/opensearch:2.13.0 |
9201 | Full-text log indexing |
| Observability | perfshop-opensearch-dashboards |
opensearchproject/opensearch-dashboards:2.13.0 |
5601 | OpenSearch UI |
| Observability | perfshop-vector |
timberio/vector:0.38.0-alpine |
— | Docker logs collection → OpenSearch |
| Observability | perfshop-opensearch-seed |
python:3.11-slim |
— | Index templates and dashboards init (one-shot) |
| QA — JMeter | perfshop-jmeter |
justb4/jmeter:5.5 |
— | Permanent JMeter container (idle) |
| QA — JMeter | perfshop-jmeter-ui |
node:20-alpine (mounted code) |
3005 | Custom JMeter control UI |
| QA — Squash | perfshop-squash-db |
postgres:16 |
5433 | Squash TM database |
| QA — Squash | perfshop-testmgmt |
squashtest/squash-tm:latest |
8088 | Squash TM ALM |
| QA — Squash | perfshop-orchestrator |
squashtest/squash-orchestrator:latest |
— | SSH execution control |
| QA — Squash | perfshop-squash-seed |
python:3.11-slim |
— | Squash TM project init (one-shot) |
| QA — Test exec | perfshop-selenium |
selenium/standalone-chrome:latest |
4444 + 7900 | Selenium Grid + noVNC |
| QA — Test exec | perfshop-test-runner |
perfshop-test-runner:latest |
— | Robot Framework + pytest + SSH |
| QA — SCM | perfshop-forgejo |
codeberg.org/forgejo/forgejo:14 |
3009 | Self-hosted Git |
| QA — SCM | perfshop-forgejo-seed |
python:3.11-slim |
— | CI account + repo + token init (one-shot) |
| QA — Scripts | perfshop-scripts-ui |
node:20-alpine (mounted code) |
3008 | Script editor/launcher |
Network and volumes¶
flowchart TB
subgraph net["perfshop-network<br/>(bridge)"]
direction LR
core["Core<br/>(8 services)"]
obs["Observability<br/>(11 services)"]
qa["QA<br/>(11 services)"]
end
subgraph vol["Named volumes"]
direction TB
v1["mysql-data"]
v2["prometheus-data"]
v3["grafana-data"]
v4["loki-data"]
v5["tempo-data"]
v6["pyroscope-data"]
v7["squash-db-data"]
v8["forgejo-data"]
v9["forgejo-token-data"]
v10["jmeter-ui-modules"]
v11["scripts-ui-modules"]
v12["opensearch-data"]
end
core --> v1
obs --> v2
obs --> v3
obs --> v4
obs --> v5
obs --> v6
obs --> v12
qa --> v7
qa --> v8
qa --> v9
qa --> v10
qa --> v11
12 named volumes (the exact numbers vary slightly between desktop.yml and build.yml around the mysql-data volume). All are managed by the Docker engine — no sensitive bind mount on the host side, except for read-only configuration bind mounts (./prometheus/prometheus.yml, ./grafana/dashboards, ./loki/loki-config.yml, etc.) and the shared volumes ./jmeter/{scenarios,scripts,results,plugins,logs} and ./test-runner/{scripts,logs}.
Core group¶
perfshop-db — MySQL 8¶
- Official
mysql:8.0image, healthcheckmysqladmin ping(interval 10 s, timeout 5 s, 5 retries) - Variables:
MYSQL_DATABASE=perfshop,MYSQL_USER=perfshop,MYSQL_PASSWORD=${DB_PASSWORD},MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD} - Named volume
mysql-data→/var/lib/mysql cpuset: "10"limit (demonstrative CPU pinning)- JSON-file logging capped at 20 MB × 3 files
perfshop-app — Spring Boot backend¶
This is the most complex service in the stack. See also auth.md, database.md, and the Observability section.
- Custom image
perfshop-backend:v18 - Limits:
mem_limit: 1536m,mem_reservation: 512m,cpuset: "11" - Exposed ports: 8080 (REST API) and 9090 (Spring Boot Actuator management port)
- Healthcheck: none, handled by ordered dependency
- Dependencies:
perfshop-db(healthy),perfshop-tempo,perfshop-pyroscope
JAVA_OPTS — embedded APM agents¶
The image embeds two Java agents that are activated via the JAVA_OPTS variable:
-Xms256m -Xmx1g -XX:MaxMetaspaceSize=128m
-javaagent:/agents/opentelemetry-javaagent.jar
-Dotel.service.name=perfshop
-Dotel.exporter.otlp.endpoint=http://perfshop-tempo:4317
-Dotel.exporter.otlp.protocol=grpc
-Dotel.traces.exporter=otlp
-Dotel.metrics.exporter=none
-Dotel.logs.exporter=none
-Dotel.instrumentation.http.capture-headers.server.request=X-Admin-Token,Content-Type
-Dotel.instrumentation.jdbc.captured-statements.enabled=true
-Dotel.span.attribute.count.limit=256
-javaagent:/agents/pyroscope.jar
-Dpyroscope.server.address=http://perfshop-pyroscope:4040
-Dpyroscope.application.name=perfshop
-Dpyroscope.format=jfr
-Dpyroscope.profiler.event=cpu
-Dpyroscope.profiler.alloc=512k
-Dpyroscope.profiler.lock=10ms
-Dpyroscope.profilingInterval=PT0.02S
Details:
- OpenTelemetry agent: exports OTLP gRPC traces to Tempo (port 4317), disables metrics and logs export (Prometheus and Loki are the preferred sinks), captures the
X-Admin-TokenandContent-Typeheaders on the server side, and captures JDBC statements. Seeobservability/tempo.md. - Pyroscope agent: exports CPU profiles in JFR (Java Flight Recorder) format, with sampling every 20 ms (
PT0.02S), allocation profile every 512 KB and lock profile for contention ≥ 10 ms. Seeobservability/pyroscope.md.
Application environment variables¶
| Variable | Role |
|---|---|
SPRING_DATASOURCE_URL |
jdbc:mysql://perfshop-db:3306/perfshop?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC |
SPRING_DATASOURCE_USERNAME / _PASSWORD |
Application account perfshop |
PERFSHOP_LANG |
fr or en (selects Flyway migrations and i18n bundles) |
CORS_ALLOWED_ORIGINS |
Composed dynamically from PUBLIC_FRONTEND_URL, PUBLIC_MONITORING_URL, PUBLIC_CHAOS_URL, PUBLIC_ADMIN_URL |
SESSION_COOKIE_SECURE / _SAME_SITE |
Cookie policy (depending on HTTP/HTTPS) |
PERFSHOP_ADMIN_EMAIL / _PASSWORD |
Superadmin bootstrap at first startup |
PERFSHOP_LICENSE_KEY |
License key (takes precedence over the perfshop_license table) |
PUBLIC_GAMES_URL |
External URL of the games hub (used only in the end-of-journey chaining) |
Product images bind mount¶
The backend can write images uploaded via admin into this folder; the frontend nginx serves them directly from the shared volume.
perfshop-frontend — React shop¶
- Custom image
perfshop-frontend:v17, based onnginx:alpine - Limits:
mem_limit: 256m,mem_reservation: 64m,cpuset: "1" - Bind mount
./frontend/public/images→/usr/share/nginx/html/images(images shared with the backend) VITE_API_URL,VITE_MONITORING_URL,VITE_LANGvariables injected via theenv-inject.shscript at container startup (sedsubstitution into the bundled Vite JS files)
perfshop-monitoring — Real-time Node dashboard¶
- Custom image
perfshop-monitoring:v4 - Bind mount
./monitoring/public→/app/public(static HTML/CSS/JS) - Bind mount
/var/run/docker.sock(read-only): enables querying the Docker API to retrieve CPU/RAM/network statistics for all containers - Polls
http://perfshop-app:9090/actuator/prometheusevery 2 seconds (POLL_INTERVAL=2000) - Exposes
/metricsin Prometheus format (perfshop-dockerjob on the Prometheus side) - See
observability/dashboard-html.mdfor the details
perfshop-admin and perfshop-chaos-admin¶
These two services are vanilla HTML/CSS/JS bundles served by nginx, with an env-inject-nginx.sh script that substitutes the PUBLIC_* variables in the HTML files at startup. They have no server-side logic — all business logic lives in the Spring Boot backend, which they consume via REST.
perfshop-welcome¶
- Custom image
perfshop-welcome:v6 dnsoripmode depending onWELCOME_MODE(the page lists accessible URLs using eitherlocalhost/IP or the configured DNS subdomains)- Receives all
PUBLIC_*_URLvariables from the.envto generate its directory
docs — MkDocs documentation¶
- Official image
squidfunk/mkdocs-material:latest - Bind mount
./mkdocs→/docs - Command:
serve --dev-addr=0.0.0.0:8000 - This is the service that serves this very documentation
Observability group¶
See Observability — Overview for a functional analysis. This sub-section only gives the Docker details.
prometheus¶
- Named volume
prometheus-data→/prometheus - Bind mount
./prometheus/prometheus.yml→/etc/prometheus/prometheus.yml - Explicit command with
--storage.tsdb.retention.time=7d,--storage.tsdb.retention.size=5GB,--web.enable-remote-write-receiver,--web.enable-lifecycle - Details in
observability/prometheus.md
grafana¶
- Pinned version 12.0.0 (other obs images are on
latest, Grafana is fixed for dashboard stability) - Key variables:
GF_AUTH_ANONYMOUS_ENABLED=true,GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer,GF_PANELS_DISABLE_SANITIZE_HTML=true,GF_USERS_DEFAULT_LANGUAGE=${PERFSHOP_UI_LOCALE:-en-US} - Bind mounts:
./grafana/provisioning(datasources + dashboards.yml) and./grafana/dashboards(10 JSON files) - Healthcheck
/api/health - Details in
observability/grafana.md
perfshop-grafana-seed¶
- Image
python:3.11-slim, installsrequests==2.31.0at startup then runs/app/seed.py restart: "no"(one-shot)depends_on: grafana: condition: service_healthy- Role: applies the Admin-only ACL on the Instructors folder and configures the home dashboard
- Details in
observability/grafana.md
perfshop-loki / perfshop-promtail¶
- Loki in single-node mode (
target: all), retention 168 h (7 days), TSDB v13 schema - Promtail collects via Docker SD socket, plus two static_configs jobs for
jmeter.logand the Test Runner logs (/rf-logs/*.log) - Promtail bind mounts:
/var/run/docker.sock(RO),./jmeter/logs(RO),./test-runner/logs(RO) - Details in
observability/loki.md
perfshop-tempo¶
- Pinned version 2.4.2
- 3 exposed ports:
19200(HTTP query),4317(OTLP gRPC),4318(OTLP HTTP) metrics_generatorenabled (service-graphs + span-metrics) with remote_write to Prometheus- Details in
observability/tempo.md
perfshop-pyroscope¶
- Filesystem backend
/var/pyroscope/data scrape_configs: [](no scrape, push-only mode from the Java agent)- Details in
observability/pyroscope.md
perfshop-opensearch / _dashboards / _seed / vector¶
A complete OpenSearch stack parallel to Loki:
perfshop-opensearch: 2.13.0, single-node, security plugin disabled, healthcheck/_cluster/health, ulimitsmemlock=-1andnofile=65536perfshop-opensearch-dashboards: 2.13.0, Kibana-compatible UIperfshop-vector: collects Docker logs (mounted socket) and pushes them intoperfshop-{service_family}indices after VRL transformperfshop-opensearch-seed: creates 7 index templates (spring, nginx, mysql, jmeter, qa, forgejo, observability) and imports theperfshop-all-logs.ndjsondashboard- Details in
observability/opensearch.md
QA group¶
See QA stack — Overview for the full workflow.
perfshop-jmeter — Permanent container¶
- Image
justb4/jmeter:5.5 - Specific entrypoint:
/bin/sh -c "cp /plugins/*.jar /opt/apache-jmeter-5.5/lib/ext/ 2>/dev/null || true && echo '[JMeter] Plugins installed' && tail -f /dev/null" - The container stays idle (
tail -f /dev/null) waiting for a test run to be launched viadocker exec. The plugins (including the Prometheus listener plugin) are copied once at startup. - Bind mounts:
./jmeter/{scenarios,scripts,results,plugins,logs} - Heap:
HEAP=-Xms256m -Xmx${JMETER_MAX_RAM:-1024}m -XX:MaxMetaspaceSize=128m - Details in
qa/jmeter.md
perfshop-jmeter-ui¶
- Base
node:20-alpine, source code mounted as a volume - Command:
sh -c "npm install --omit=dev && node src/server.js" node_modulespersisted in the named volumejmeter-ui-modules(avoids reinstalling on every restart)- Bind mount
/var/run/docker.sock: required to rundocker exec perfshop-jmeter ...which launches the test runs - Variables:
JMETER_TARGET_INTERNAL,JMETER_TARGET_EXTERNAL,JMETER_PROMETHEUS_PORT=9270,PROMETHEUS_INTERNAL_URL=http://perfshop-prometheus:9090,JMETER_MAX_RAM,GRAFANA_URL - Details in
qa/jmeter.md
perfshop-squash-db / perfshop-testmgmt¶
- PostgreSQL 16 dedicated to Squash TM (deliberately separated from MySQL to respect the official Squash stack)
- Squash TM uses the
./squash-seed/start-plugins.cfgand./squash-seed/squash.tm.cfg.propertiesbind mounts for its configuration - Critical variable:
SQUASH_JWT_SECRET(base64 secret used to sign the JWT tokens accepted by the orchestrator) - Healthcheck
/squash/isSquashAlive - Details in
qa/squash-tm.md
perfshop-orchestrator¶
- Official image
squashtest/squash-orchestrator:latest - 5 configuration bind mounts:
pools.yaml,sshee.yaml,arranger.yaml,squashtf.yaml,trusted_key.pub - Drives Squash TM via the
SQUASH_TM_URL,SQUASH_TM_USER,SQUASH_TM_PASSWORDvariables - Details in
qa/squash-orchestrator.md
perfshop-selenium¶
- Official image
selenium/standalone-chrome:latest - 2 ports: 4444 (Selenium WebDriver) and 7900 (noVNC for visual debugging)
- Variables:
SE_NODE_MAX_SESSIONS=2,SE_VNC_NO_PASSWORD=1,SE_START_XVFB=true shm_size: 2gb(Chrome needs significant shared memory to avoid crashes)- Details in
qa/selenium.md
perfshop-test-runner¶
- Custom image
perfshop-test-runner:latest(locally built on the machine, not pullable from a public registry) - Includes OpenSSH server, Robot Framework 7.0, robotframework-seleniumlibrary 6.3.0, robotframework-requests 0.9.6, robotframework-jsonlibrary 0.5, pytest 8.0, selenium 4.18.1, allure-robotframework, squash-tf-services
- Bind mounts:
./test-runner/scripts→/scripts,./test-runner/logs→/rf-logs - Variables:
SELENIUM_REMOTE_URL=http://perfshop-selenium:4444/wd/hub,PERFSHOP_FRONTEND_INTERNAL,PUBLIC_API_URL,PERFSHOP_LANG - Details in
qa/test-runner.md
perfshop-forgejo / perfshop-forgejo-seed¶
- Forgejo 14, SQLite database (
/data/gitea/forgejo.db) - Critical variables:
FORGEJO__server__ROOT_URL,FORGEJO__database__DB_TYPE=sqlite3,FORGEJO__security__INSTALL_LOCK=true,GITEA_ADMIN_USERNAME/_PASSWORD/_EMAIL - Healthcheck via
curl http://localhost:3000/api/v1/version - The seed creates the CI account, generates an API token stored in
/token/forgejo_token(named volumeforgejo-token-datashared withperfshop-scripts-ui), creates theperfshop-testsrepository, and pushes the initial scripts - Details in
qa/forgejo.md
perfshop-scripts-ui¶
- Base
node:20-alpine, source code mounted as a volume, identical tojmeter-ui - Bind mount
/var/run/docker.sock(fordocker exec perfshop-test-runner) - Shared volume
./test-runner/logs→/rf-logs(reading RF outputs + writing "persistent runs") - Read-only named volume
forgejo-token-data:/token:ro(reads the Forgejo token generated by the seed) - Variables:
FORGEJO_INTERNAL_URL,FORGEJO_CI_USER,FORGEJO_REPO,FORGEJO_TOKEN_FILE=/token/forgejo_token,TEST_RUNNER_CONTAINER=perfshop-test-runner,SELENIUM_REMOTE_URL - Details in
qa/scripts-ui.md
Pedagogical games hub¶
The games hub is embedded in the stack but is a standalone application: no backend dependency, no DB access, no server authentication. It is a static web bundle served by nginx, built on Phaser 3.80 and Three.js 0.160, packaged with Vite 5.
The detailed internal architecture of the hub (engine, scenes, Phaser components, asset format) will be covered on a dedicated page in a future section. The presence of this service in the compose file and its documentation never mention its public URL, its port, or its Docker service name — access happens only through the end-of-journey chaining handled by the backend.
Startup order and depends_on¶
The full dependency graph is described in deployment.md. Key points to remember:
- Healthchecked services (
perfshop-db,grafana,perfshop-testmgmt,perfshop-squash-db,perfshop-forgejo,perfshop-opensearch) block their consumers from starting. - Seeds (
grafana-seed,squash-seed,forgejo-seed,opensearch-seed) wait for their targets withcondition: service_healthyorcondition: service_completed_successfully. perfshop-squash-seedexplicitly waits for the successful completion ofperfshop-forgejo-seedbefore starting, because it needs the Forgejo CI token to configure the Squash TM SCM.
To go further¶
- Deployment topology — Docker Desktop and Linux/VPS modes
- Network and domains — port details and DNS conventions
- Observability — Overview
- QA stack — Overview