Grafana k6
Developer-friendly load testing and performance tool
Grafana k6 is an open-source load testing tool that lets developers write performance tests in JavaScript. It is used for synthetic checks, regression testing, and validating system reliability under load.
Key features
- JavaScript test scripting
- Load and performance testing
- CLI and CI integration
- Metrics output to many backends
Pros & cons
Strengths
- Developer-friendly scripting
- Great CI integration
Trade-offs
- Not a continuous monitor
- Distributed runs need extra setup
Grafana k6 replaces
Last reviewed Aug 26, 2026 · 827 words
k6 will tell you in 90 seconds that your reverse proxy starts returning 502s at about 300 concurrent users. It will not tell you at 3 a.m. that it has actually happened. That distinction is the whole guide: k6 is a load-testing tool you run on purpose, before an upgrade or after a config change, and people who install it expecting an always-on health checker end up disappointed and with a very hot server.
It is a Go binary that runs JavaScript, not a Node program
k6 ships as a single Go executable (about 256 MB of RAM at idle, more as virtual users climb) and embeds its own JavaScript runtime. That means your test scripts are plain ES modules with import http from 'k6/http', but there is no npm install and no Node APIs; anything beyond the built-in modules has to be bundled first. In practice that is a feature. A test is one file, checked into the same repo as your compose stack, runnable from docker run --rm -i grafana/k6 run - < smoke.js on any box with Docker. The 31,000-star project is AGPL-3.0 and Grafana Labs maintains it, so the free tool and the paid Grafana Cloud k6 share the same scripts.
The 20-line script that pays for itself
Here is the test I run against anything I put behind a proxy, adjusted to a cheap endpoint the app itself exposes:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 50 },
{ duration: '1m', target: 200 },
{ duration: '30s', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('https://photos.example.com/api/server/ping');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
Ramp to 200 virtual users over 90 seconds, each hitting the endpoint once a second. The two thresholds lines are the part worth copying: if the 95th-percentile response time crosses 500 ms or more than 1% of requests fail, k6 exits non-zero. A non-zero exit is what lets a load test sit in a CI job and block a deploy, which is the "great CI integration" that gets mentioned about k6 and is the reason it beat the GUI-driven tools it replaced.
What a self-hoster actually learns from a run
The interesting failures are never in the app. Three I have caught with scripts like the one above: a Traefik instance with default timeouts queueing requests behind a slow upstream until everything went 504; an app on SQLite where 40 concurrent writes locked the database and every read stalled behind them; and a nightly backup job that overlapped my test window and halved throughput, which is how I learned the backup was running 2 hours longer than I thought. None of those show up on a dashboard that only samples once a minute.
Test your own services only. Running this against a site you do not own is indistinguishable from an attack, and hosting providers treat it as one.
Results go to Prometheus or Grafana with one flag
The terminal summary is fine for a one-off. For anything you want to compare across weeks, ship the metrics out: k6 run -o experimental-prometheus-rw script.js with K6_PROMETHEUS_RW_SERVER_URL pointing at a Prometheus instance that has remote-write receiving enabled, then graph k6_http_req_duration_p95 in Grafana next to your CPU and container metrics. InfluxDB and JSON outputs exist too. Seeing request latency on the same time axis as the proxy's CPU is what turns "it got slow" into "it got slow because the ML container pegged all four cores".
The monitor you still need is a different tool
k6's own docs are careful about this: it is not a continuous monitor. For "is it up right now, tell my phone", you want Uptime Kuma or Gatus, which poll every 30 to 60 seconds with one request and cost nothing. The homelab monitoring post covers that side. k6 is the tool you run once a month, and once before anything scary.
Distributed runs are the other honest limit. One machine can drive a few thousand virtual users depending on script complexity; past that you need the k6-operator on Kubernetes or the paid cloud runner, and both are more setup than a homelab wants.
What I'd do
Keep a loadtests/ directory beside the compose file, one script per service, thresholds set to what you would actually accept. Run the set before every major upgrade and after any reverse-proxy change, with output to Prometheus so you can see the trend. Leave the always-on alerting to Uptime Kuma. Used that way, k6 is the cheapest insurance in the monitoring category: 20 lines of JavaScript and 90 seconds, and you know your limit before your users do.
Similar monitoring & status apps
Uptime Kuma
Monitoring & StatusEasy self-hosted uptime monitoring tool
Replaces Pingdom, UptimeRobot
Netdata
Monitoring & StatusReal-time per-second infrastructure monitoring
Replaces Datadog, New Relic
Grafana
Monitoring & StatusOpen observability dashboards and visualization
Replaces Datadog
Prometheus
Monitoring & StatusMetrics-based monitoring and alerting toolkit
Replaces Datadog
Glances
Monitoring & StatusCross-platform system monitoring at a glance
Replaces Datadog
InfluxDB
Monitoring & StatusPurpose-built time series database for metrics and events
Replaces Datadog, AWS Timestream