PerfLoad No script HTTP load testing

Installation Guide

Detailed setup instructions for running PerfLoad locally with Docker, and deploying it to a Kubernetes cluster.

Docker

The fastest way to run PerfLoad. A single pre-built image, one port, one volume.

Prerequisites

1. Run the container

docker run -d --name perfload \
  -p 3000:3000 \
  -v perfload-runs:/app/runs \
  perfload/perfload-runner:latest

To pin a specific release instead, replace latest with a version tag, e.g. 1.6.0.

2. Verify it's up

curl http://localhost:3000/health

Expected response: {"status":"ok"}

InterfaceURL
Dashboardhttp://localhost:3000/
Workbenchhttp://localhost:3000/load-tester.html
k6 live dashboardhttp://localhost:3000/runs/<id>/dashboard/live/

3. Update to a new version

docker pull perfload/perfload-runner:latest
docker stop perfload && docker rm perfload
docker run -d --name perfload \
  -p 3000:3000 \
  -v perfload-runs:/app/runs \
  perfload/perfload-runner:latest

Run history in the perfload-runs volume survives this — it's independent of the container.

4. Managing the container

# Stop, keep the volume
docker stop perfload

# Start again
docker start perfload

# View logs
docker logs perfload

# Remove the container (volume persists)
docker rm perfload

# Only if you want run history gone too
docker volume rm perfload-runs

Kubernetes

Deployed via kustomize from cd-deploy-configs/apps/perfload-runner/. A docker run gives the container the whole host's CPU, memory, and a writable layer that survives until removed — none of that is true by default in a k8s pod, which shares the node and starts from a clean, ephemeral filesystem on every reschedule. The manifests and checklist below cover what has to be made explicit to get equivalent behavior in the cluster.

Prerequisites

Manifests

cd-deploy-configs/apps/perfload-runner/ holds the following five files. If the directory doesn't exist yet for a first-time deploy, create them.

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: perfload-runner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: perfload-runner
  template:
    metadata:
      labels:
        app: perfload-runner
    spec:
      containers:
        - name: perfload-runner
          image: perfload/perfload-runner:latest
          ports:
            - containerPort: 3000
          resources:
            requests:
              cpu: 500m
              memory: 768Mi
            limits:
              cpu: "1"
              memory: 1Gi
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
          envFrom:
            - configMapRef:
                name: app-configmap
          volumeMounts:
            - name: runs
              mountPath: /app/runs
            - name: dshm
              mountPath: /dev/shm
      volumes:
        - name: runs
          persistentVolumeClaim:
            claimName: perfload-runner-runs
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: 512Mi

The resources block is the part most worth adjusting per environment — see two alternative sizing profiles below.

deployment.yaml — light profile (dev / low-traffic, occasional single runs)
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 500m
              memory: 768Mi
deployment.yaml — heavy profile (frequent/concurrent runs, regular PDF exports)
          resources:
            requests:
              cpu: "1"
              memory: 1.5Gi
            limits:
              cpu: "2"
              memory: 2.5Gi

See "Resource requests/limits" below for why k6 (CPU) and Puppeteer/Chromium (memory) are the two costs to size for.

pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: perfload-runner-runs
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

accessModes should stay ReadWriteOnce in every profile below — see "Ephemeral vs. persistent storage" further down for why. Only storage changes with run volume and retention.

pvc.yaml — short retention (few runs kept, cleaned up often)
  resources:
    requests:
      storage: 2Gi
pvc.yaml — long retention (high run volume, dashboard exports kept)
  resources:
    requests:
      storage: 20Gi

Each run directory holds its generated k6 script, summary, raw metrics stream, and (if kept) the exported dashboard HTML — dashboard exports are the largest of these, so retention needs scale mostly with how many dashboards you keep, not run count alone.

service.yaml

Exposes the pod inside the cluster. How it's reached from outside (Ingress, LoadBalancer, etc.) is cluster-specific and not covered here.

apiVersion: v1
kind: Service
metadata:
  name: perfload-runner
spec:
  selector:
    app: perfload-runner
  ports:
    - port: 3000
      targetPort: 3000
configmap.yaml

The deployment's envFrom requires this to exist even if empty, since a missing ConfigMap reference blocks the pod from starting. Every key below has a safe in-code default (see "Config differences" below) — only add one if you're deliberately overriding it.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-configmap
data: {}
# To override a default, replace `data: {}` above with e.g.:
# data:
#   PORT: "3000"
#   K6_DASHBOARD_PORT_START: "5665"
#   K6_DASHBOARD_PORT_COUNT: "20"
kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - pvc.yaml
  - service.yaml
  - configmap.yaml

After deploying: differences from Docker to check

A k8s pod shares the node with other pods and starts from a clean, ephemeral filesystem every time it's rescheduled, unlike docker run above. Work through this checklist once the manifests are applied.

1. Resource requests/limits

2. Ephemeral vs. persistent storage

3. /dev/shm / shared memory

4. Health checks