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://<host>:<port>| svc
end
Whatever the target, the topology stays identical:
- A single
perfshop-networkDocker 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) — seenetwork.mdfor 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 theservice_healthystatus 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:<PORT>"]
browser --> run
end
Launch¶
The build.sh script in desktop mode:
- Checks for the presence of the
.envfile; otherwise copies.env.exampleto.envand offers an interactive prompt (IP, language, license key). - Locally builds the custom images from the Dockerfiles present in the
backend/,frontend/,admin/,chaos-admin/,monitoring/,welcome/,games/,test-runner/folders. - Loads the
docker-compose.desktop.ymlcompose file. - Runs
docker compose up -dto start everything in the background. - Displays the URL of the
perfshop-welcomepage that acts as a directory pointing to all exposed services.
Silent mode for automation:
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¶
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:
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-appwaits for MySQL to respond tomysqladmin ping, and for Tempo and Pyroscope to be running (so the first traces and profiles aren't lost).perfshop-grafana-seedwaits for Grafana to respond to/api/healthbefore applying the Instructors folder ACL and configuring the home dashboard.perfshop-testmgmt(Squash TM) waits for PostgreSQL;perfshop-squash-seedwaits for Squash TM to be healthy AND for the successful completion ofperfshop-forgejo-seed(which creates the CI account used for the SCM connection).perfshop-orchestratorwaits for Squash TM to be healthy and Test Runner to be started.perfshop-scripts-uiwaits forperfshop-app, Forgejo healthy and Test Runner started.perfshop-vectorandperfshop-opensearch-dashboardswait for OpenSearch to be healthy;perfshop-opensearch-seedwaits 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