JMeter in Docker: bake the plugins in, or watch CI fail on a fresh runner
Why a bare JMeter Docker image is not enough for CI — the plugins, JDBC drivers and protocol jars have to be in the image. How to run JMeter in Docker properly, what a complete image contains, and when a pre-built multi-tool image is worth paying for.
Mark
Performance Testing Expert
A stock JMeter container gets you HTTP samplers and the non-GUI CLI. It does not get you Kafka, gRPC, AMQP, Redis, WebSocket, or the JDBC drivers your test plan already assumes are there. On a laptop you install those once through the Plugin Manager and forget about them. On CI, every clean runner starts empty — so either the pipeline downloads plugins at run time (slow, flaky, and blocked on any runner without internet access), or the image already contains them.
This post is about that choice: what “JMeter in Docker” has to include before it is useful in CI, how to run it without fighting the entrypoint, and when a pre-built image with the protocol set already present is the rational buy.
Rewritten September 2026. The 2020 version of this page recommended a third-party Docker Hub image and showed the three execution modes. The modes have not changed; the advice has, because “which image” turned out to be the whole problem.
The plugins are part of the test asset
If a plan was built or extended with WebSocket, gRPC, Kafka, AMQP, JMS, MongoDB, Redis, PostgreSQL, MySQL, FTP, SMTP, LDAP or raw TCP samplers, the jars that implement those samplers are as much a part of the test as the CSV feed. Leaving them out of the image is the same class of mistake as shipping the JMX without its data file — except that the CSV failure is loud and immediate, and the missing-plugin failure is a ClassNotFoundException that only appears when someone merges the first non-HTTP sampler.
The failure mode, step by step
- Local JMeter has the plugins. The CI image does not.
- The job works for every HTTP-only plan and fails red the day someone adds a Kafka or JDBC sampler.
- Someone “fixes” it with a
PluginsManagerCMD.sh installstep in the job. It works until the runner has no route tojmeter-plugins.org, or the plugin version drifts between builds, or the download takes four minutes on every run. - Distributed workers on Kubernetes each need the identical plugin set. Downloading per pod is how you get one worker that cannot run the plan and a controller that waits on it forever.
Bake once. Pin versions. Pull the same tag everywhere.
What a complete JMeter image actually contains
The minimum for CI:
- A pinned JMeter version — not
latestfloating under you halfway through a programme. - The non-GUI CLI as the entrypoint, writing a
.jtlyou can archive. - The same plugin and driver set as the machines that author the plans.
- A way to pass host, port and properties in without rebuilding —
-Jproperties or environment variables the plan already reads.
Common additions:
- A metrics path (Backend Listener to InfluxDB, or Prometheus) so the run is not a black box until the JTL lands. The JMeter with Prometheus and Grafana post covers that.
- Multi-arch (
amd64andarm64) if your runners are mixed — increasingly they are.
For concreteness, this is what the perf-jmeter image ships, taken from its Dockerfile rather than from memory. JMeter 5.6.3 with Plugin Manager 1.10 and, installed at build time:
| Plugin set | What it gives you |
|---|---|
jpgc-standard, jpgc-graphs-basic/additional/composite | The jp@gc listeners and graphs |
jpgc-tst, jpgc-casutg | Throughput Shaping Timer, Concurrency and Ultimate Thread Groups |
jpgc-json, jpgc-functions, jpgc-csvars, jpgc-fifo, jpgc-dummy | JSON handling, extra functions, inter-thread communication, a dummy sampler for plan development |
bzm-parallel | Parallel controller |
jpgc-perfmon, jpgc-cmd, jpgc-synthesis, jpgc-filterresults, jpgc-mergeresults | Server monitoring and results post-processing from the CLI |
Plus the protocol jars that are not Plugin Manager packages and have to be fetched individually: WebSocket samplers 1.2.10, the AMQP plugin 0.3.0 with amqp-client 5.20.0, the Redis plugin 0.5, gRPC 1.60.0 (netty-shaded, protobuf, stub), the MongoDB driver 4.11.1, PostgreSQL JDBC 42.7.1, MySQL JDBC 8.2.0, Kafka client 3.6.1, and ActiveMQ Artemis 2.31.2 for JMS. That is seventeen protocols, and the version pins are the point: a plan that ran against this tag will run against this tag next year.
Keep an inventory like this next to your own Dockerfile. When a sampler class is missing under load at 2am, “was it in the image?” should take thirty seconds to answer.
Run JMeter in Docker without the GUI
docker run --rm \
-v "$PWD/tests:/tests:ro" \
-v "$PWD/results:/results" \
your-jmeter-image:tag \
jmeter -n -t /tests/plan.jmx -l /results/results.jtl \
-JTARGET_HOST=api.example.com -JTARGET_PORT=443
-nis non-GUI. There is no reason to run the GUI in a container.- Mount the plan read-only and the results directory writable. Do not rely on
docker cpas the only way results get out; a job that is killed mid-run should still leave the partial.jtlon the volume. - Pass hosts and ports with
-Jproperties the plan reads through${__P(TARGET_HOST)}. Rebuild the image for plugins, never for a hostname. - If the image also carries Taurus (
bzt) — the Martkos images do, on the base layer — pick one path per job and write it down. A pipeline where some jobs calljmeterand some callbztagainst the same plan is a pipeline nobody can reason about.
Open the resulting .jtl in the free report viewer to read it without installing anything, or generate the HTML dashboard with -e -o /results/report on the same command.
When one container is not enough
Spreading a plan across several JMeter workers on Kubernetes is a different problem — RMI callbacks, headless Services, and the controller having to reach every worker by a stable name. The distributed load testing on Kubernetes post covers it. The image rule does not change: every worker starts from the same plugin-complete tag, and the controller runs the same one.
Multi-tool teams
JMeter plus k6 (with xk6 extensions) plus Gatling (with JDBC drivers and messaging clients on the classpath) means three Dockerfiles, three plugin inventories, and three places for versions to drift. If that is the shape of your team, one private registry publishing pinned, tested images is an operations decision rather than a build task. Perf Containers is that layer: JMeter 5.6.3 with the seventeen protocols above, k6 with xk6 extensions, Gatling 3.10.3, all multi-arch, £300 a year on registry.martkos-it.co.uk. Use it when maintaining three images costs more than the subscription. Ignore it for a single HTTP-only JMeter job — the stock image is fine for that, and this post has told you what to add if it stops being fine.
Checklist
- The plan runs in the container with no Plugin Manager step in the job.
- The same image tag is used for the authoring smoke test and the CI run.
- The
.jtland any report land on a volume or artifact store, not inside the container. - Host, port and secrets come from job configuration, not from a rebuild per environment.
- If you distribute: the same tag on every worker, and read the Kubernetes RMI notes before you scale.
Tags: