Skip to content

Deployment topology

PerfShop ships with two deployment topologies maintained in parallel, which share the exact same list of services and the same naming conventions:

Target Compose file Mode Audience
Docker Desktop docker-compose.desktop.yml Local Windows / macOS build Independent instructor, peer-to-peer demo
Linux / VPS / CI docker-compose.build.yml Local Linux build University, training organization, self-hosted deployment

Both modes are orchestrated by the build.sh script (and its Windows counterpart build.bat), which generates the .env file from the .env.example template, builds the custom images, then runs docker compose up -d.

Source of truth

The lists of services, ports, volumes and environment variables described on this page are taken from the two compose files listed above. Both compose files have strictly the same list of application services; only the way images are produced differs (local build + node_modules cache in named volumes).

Overview

flowchart TB
  subgraph dev["User's machine / server"]
    direction TB
    user["Browser<br/>(instructor or student)"]

    subgraph engine["Docker Engine"]
      direction TB
      net["perfshop-network<br/>(dedicated bridge)"]
      svc["31 PerfShop services<br/>(shop, chaos, obs, QA, games)"]
      vol["Named volumes + bind mounts<br/>(persisted data)"]
      net --- svc
      svc --- vol
    end

    user -->|http://&lt;host&gt;:&lt;port&gt;| svc
  end

Whatever the target, the topology stays identical:

  • A single perfshop-network Docker bridge network isolates inter-service communications and enables DNS resolution by container name.
  • Each application service exposes its port on the host according to environment variables (*_HTTP_PORT) — see network.md for the full table.
  • Persistent data (MySQL, Grafana, Loki, Tempo, Pyroscope, OpenSearch, Forgejo, Squash DB, etc.) lives in named Docker volumes managed by the engine — no sensitive bind mount on the host side.
  • Seeds (perfshop-grafana-seed, perfshop-squash-seed, perfshop-forgejo-seed, perfshop-opensearch-seed) run only once at first startup (restart: "no") and wait for the service_healthy status of their targets before launching their initialization.

Target 1 — Docker Desktop (Windows / macOS)

flowchart LR
  subgraph host["Developer / instructor machine"]
    subgraph dd["Docker Desktop"]
      direction TB
      img["PerfShop images<br/>locally built<br/>perfshop-backend, perfshop-frontend,<br/>perfshop-admin, perfshop-chaos-admin,<br/>perfshop-monitoring, perfshop-welcome,<br/>perfshop-test-runner"]
      run["31 running containers"]
      img --> run
    end
    browser["Chrome / Firefox browser<br/>http://localhost:&lt;PORT&gt;"]
    browser --> run
  end

Launch

./build.sh desktop

The build.sh script in desktop mode:

  1. Checks for the presence of the .env file; otherwise copies .env.example to .env and offers an interactive prompt (IP, language, license key).
  2. Locally builds the custom images from the Dockerfiles present in the backend/, frontend/, admin/, chaos-admin/, monitoring/, welcome/, games/, test-runner/ folders.
  3. Loads the docker-compose.desktop.yml compose file.
  4. Runs docker compose up -d to start everything in the background.
  5. Displays the URL of the perfshop-welcome page that acts as a directory pointing to all exposed services.

Silent mode for automation:

./build.sh desktop --silent --ip=192.168.1.10 --license=PFSH-xxx.yyy --lang=fr

Docker Desktop specifics

Aspect Detail
Custom images Built on the machine via docker build (Docker Desktop supports all required seccomp profiles and capabilities)
mysql-data volume Bind mount on the local filesystem to allow direct inspection of MySQL data when needed
node_modules Persisted in named volumes (jmeter-ui-modules, scripts-ui-modules) to avoid reinstalling Node dependencies on every restart
Performance Docker Desktop's virtualized filesystem is slower for massive bind mounts; named volumes are preferred for hot data
Networking localhost from the browser side, host.docker.internal if a container needs to call a resource on the host machine

Target 2 — Linux / VPS / CI

flowchart LR
  subgraph vps["Linux server or VPS"]
    subgraph engine2["Native Docker Engine"]
      direction TB
      img2["PerfShop images<br/>locally built on the server"]
      run2["31 running<br/>containers"]
      img2 --> run2
    end
    rp["Reverse proxy<br/>(optional)<br/>nginx, Caddy, Traefik..."]
    rp --> run2
  end
  internet["Students / instructors<br/>(browser)"] -->|HTTPS| rp

Launch

./build.sh unix

The build.sh script in unix mode follows the same flow as desktop mode, but uses the docker-compose.build.yml compose file adapted to the native Linux Docker engine. This is the recommended mode for:

  • An installation on a VPS dedicated to training (training organization, university)
  • A continuous integration pipeline that validates the full stack on every commit
  • A deployment on an internal Linux machine (lab, classroom)

Silent mode:

./build.sh unix --silent --ip=10.0.0.5 --lang=en --license=PFSH-xxx.yyy

Upstream reverse proxy

On a VPS, you typically place an external reverse proxy (nginx, Caddy, Traefik…) in front of the PerfShop stack to:

  • Terminate TLS and expose the platform over HTTPS
  • Map readable subdomains to application ports (shop.example.com → :8090, grafana.example.com → :3002, etc.)
  • Share Let's Encrypt certificates
  • Filter sensitive tools by IP (chaos-admin, admin, Squash TM)

This layer is outside the PerfShop scope: the platform exposes plain HTTP ports, and it is up to the operator to choose and configure the reverse proxy of their choice. Domain conventions, exposed ports and the PUBLIC_*_URL variables to set in the .env are described in network.md.

Environment variables

Both modes read their configuration from a .env file at the project root, generated from the versioned .env.example template. The full detail of variables will be given in a dedicated appendix; here are the main groups:

Group Example variables Role
HTTP ports BACKEND_HTTP_PORT, FRONTEND_HTTP_PORT, GRAFANA_HTTP_PORT, CHAOS_HTTP_PORT, ADMIN_HTTP_PORT, JMETER_UI_PORT, SCRIPTS_UI_PORT, FORGEJO_HTTP_PORT, OPENSEARCH_HTTP_PORT Container port → host port mapping
Public URLs PUBLIC_API_URL, PUBLIC_FRONTEND_URL, PUBLIC_MONITORING_URL, PUBLIC_GRAFANA_URL, PUBLIC_CHAOS_URL, PUBLIC_ADMIN_URL, PUBLIC_DOCS_URL, PUBLIC_JMETER_URL, PUBLIC_SCRIPTS_URL, PUBLIC_FORGEJO_URL, PUBLIC_OPENSEARCH_URL Injected into CORS, into the React frontend (VITE_*), into the welcome page, and into backend links
Database DB_PASSWORD, DB_ROOT_PASSWORD, SQUASH_DB_PASSWORD MySQL and Squash PostgreSQL passwords
Accounts PERFSHOP_ADMIN_EMAIL, PERFSHOP_ADMIN_PASSWORD, SQUASH_ADMIN_LOGIN, SQUASH_ADMIN_PASSWORD, FORGEJO_ADMIN_USER, FORGEJO_ADMIN_PASSWORD, FORGEJO_CI_USER, FORGEJO_CI_PASSWORD Superadmin accounts and CI accounts bootstrapped at startup
Sessions SESSION_COOKIE_SECURE, SESSION_COOKIE_SAME_SITE, SESSION_SECRET HTTP session cookie policy on the backend and Node UIs
License PERFSHOP_LICENSE_KEY PerfShop license key (this variable takes precedence, otherwise the perfshop_license DB table is used)
Language PERFSHOP_LANG (fr or en), PERFSHOP_UI_LOCALE Default language for the backend, frontend, Grafana and Forgejo

Passwords

The default values in .env.example are intentionally trivial (perfshop, perfshop123, Squash2026/*) to allow immediate startup in a pedagogical environment. Any public network exposure requires changing them before the first start.

Healthchecks and startup order

Services that have "warm start" dependencies use the Compose depends_on: condition: service_healthy mechanism:

flowchart LR
  db[("perfshop-db<br/>healthcheck mysqladmin ping")] --> app["perfshop-app"]
  tempo["perfshop-tempo"] --> app
  pyro["perfshop-pyroscope"] --> app

  graf[("grafana<br/>healthcheck /api/health")] --> gseed["grafana-seed"]

  sqdb[("perfshop-squash-db<br/>healthcheck pg_isready")] --> tm[("perfshop-testmgmt<br/>healthcheck /isSquashAlive")]
  tm --> sseed["squash-seed"]
  tm --> orch["perfshop-orchestrator"]

  fg[("perfshop-forgejo<br/>healthcheck /api/v1/version")] --> fseed["forgejo-seed"]
  fseed --> sseed
  fg --> sui["perfshop-scripts-ui"]

  os[("perfshop-opensearch<br/>healthcheck /_cluster/health")] --> osd["perfshop-opensearch-dashboards"]
  osd --> oseed["opensearch-seed"]
  os --> vec["perfshop-vector"]

In practice:

  • perfshop-app waits for MySQL to respond to mysqladmin ping, and for Tempo and Pyroscope to be running (so the first traces and profiles aren't lost).
  • perfshop-grafana-seed waits for Grafana to respond to /api/health before applying the Instructors folder ACL and configuring the home dashboard.
  • perfshop-testmgmt (Squash TM) waits for PostgreSQL; perfshop-squash-seed waits for Squash TM to be healthy AND for the successful completion of perfshop-forgejo-seed (which creates the CI account used for the SCM connection).
  • perfshop-orchestrator waits for Squash TM to be healthy and Test Runner to be started.
  • perfshop-scripts-ui waits for perfshop-app, Forgejo healthy and Test Runner started.
  • perfshop-vector and perfshop-opensearch-dashboards wait for OpenSearch to be healthy; perfshop-opensearch-seed waits for both.

This sequence ensures that on the first up -d, the user has no manual initialization step to perform: Grafana has its ACL, Squash TM has its project, Forgejo has its repository and its token, OpenSearch has its index templates and its dashboards.

To go further

  • Technical stack — versions and images of each component
  • Docker Compose — exhaustive analysis of the 30 services and their volumes
  • Network and domains — port mapping, DNS conventions, internal flows
  • Local development section — Forgejo cloning, backend/frontend build, Git workflow