Skip to content

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.0 image, healthcheck mysqladmin 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-Token and Content-Type headers on the server side, and captures JDBC statements. See observability/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. See observability/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

./frontend/public/images/products → /images-data/products

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 on nginx: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_LANG variables injected via the env-inject.sh script at container startup (sed substitution 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/prometheus every 2 seconds (POLL_INTERVAL=2000)
  • Exposes /metrics in Prometheus format (perfshop-docker job on the Prometheus side)
  • See observability/dashboard-html.md for 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
  • dns or ip mode depending on WELCOME_MODE (the page lists accessible URLs using either localhost/IP or the configured DNS subdomains)
  • Receives all PUBLIC_*_URL variables from the .env to 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, installs requests==2.31.0 at 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.log and 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_generator enabled (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, ulimits memlock=-1 and nofile=65536
  • perfshop-opensearch-dashboards: 2.13.0, Kibana-compatible UI
  • perfshop-vector: collects Docker logs (mounted socket) and pushes them into perfshop-{service_family} indices after VRL transform
  • perfshop-opensearch-seed: creates 7 index templates (spring, nginx, mysql, jmeter, qa, forgejo, observability) and imports the perfshop-all-logs.ndjson dashboard
  • 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 via docker 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_modules persisted in the named volume jmeter-ui-modules (avoids reinstalling on every restart)
  • Bind mount /var/run/docker.sock: required to run docker 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.cfg and ./squash-seed/squash.tm.cfg.properties bind 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_PASSWORD variables
  • 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 volume forgejo-token-data shared with perfshop-scripts-ui), creates the perfshop-tests repository, and pushes the initial scripts
  • Details in qa/forgejo.md

perfshop-scripts-ui

  • Base node:20-alpine, source code mounted as a volume, identical to jmeter-ui
  • Bind mount /var/run/docker.sock (for docker 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 with condition: service_healthy or condition: service_completed_successfully.
  • perfshop-squash-seed explicitly waits for the successful completion of perfshop-forgejo-seed before starting, because it needs the Forgejo CI token to configure the Squash TM SCM.

To go further