TL;DR:

  • k6 is an open-source load testing tool where tests are written in JavaScript/TypeScript and run as a single Go binary — fast, scriptable, and CI-friendly
  • You write scenarios, set thresholds, and the test fails if your service doesn’t meet them — same as unit tests, but for performance
  • The free open-source version covers everything for most teams; Grafana k6 Cloud adds managed execution and dashboards

Most teams think about performance testing late — usually after a production incident. k6 is designed to make it practical to write performance tests alongside your feature code, run them in CI, and catch regressions before they reach production.

What Makes k6 Different

The established alternatives — JMeter, Locust, Gatling — all work, but they each have friction points that push teams toward treating load testing as a separate project rather than part of the development workflow.

JMeter is XML-configured and GUI-driven; tests don’t version-control cleanly. Locust is Python-based and flexible but requires a separate runtime environment. Gatling uses Scala DSL, which creates a language barrier for most teams.

k6 takes a different approach: tests are JavaScript files, the runtime is a single compiled binary written in Go, and it runs anywhere without dependencies. You write a test, commit it to your repository, and run it in CI with a one-line command. The output is a structured summary that can be consumed by any monitoring or reporting tool.

Installation

On macOS:

brew install k6

On Linux (Debian/Ubuntu):

sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkeys.openpgp.org --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6

Or via Docker:

docker pull grafana/k6

Your First Test

Create a file load-test.js:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 10,           // 10 virtual users
  duration: '30s',   // running for 30 seconds
};

export default function () {
  const res = http.get('https://api.yourservice.com/health');
  
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
  
  sleep(1); // wait 1 second between iterations
}

Run it:

k6 run load-test.js

k6 prints a summary at the end with request rates, response time percentiles (p50, p90, p95, p99), error rates, and whether your checks passed.

Setting Thresholds

Checks log pass/fail per request. Thresholds let you fail the entire test run if aggregate metrics fall outside acceptable bounds — this is what makes load tests useful in CI.

export const options = {
  vus: 50,
  duration: '1m',
  thresholds: {
    http_req_duration: ['p(95)<400'],   // 95th percentile under 400ms
    http_req_failed: ['rate<0.01'],      // error rate under 1%
    checks: ['rate>0.99'],              // 99%+ checks passing
  },
};

If any threshold is breached, k6 exits with a non-zero status code. In CI, that fails the build — the same way a failing unit test would.

Ramping Load with Scenarios

Real-world load doesn’t jump from zero to 100 users instantly. k6 supports ramping profiles that model gradual increases, sustained load, and ramp-down:

export const options = {
  stages: [
    { duration: '30s', target: 10 },   // ramp up to 10 VUs
    { duration: '1m', target: 10 },    // stay at 10 VUs
    { duration: '30s', target: 50 },   // ramp up to 50 VUs
    { duration: '2m', target: 50 },    // sustained load at 50 VUs
    { duration: '30s', target: 0 },    // ramp down
  ],
};

For more complex scenarios — different user journeys running simultaneously, constant arrival rate (arrivals per second rather than concurrent users), or executor-specific settings — k6 provides a scenarios block with multiple executor types. The constant-arrival-rate executor is particularly useful for testing API endpoints with realistic request rates rather than concurrency counts.

Testing POST Endpoints and Authentication

import http from 'k6/http';
import { check } from 'k6';

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
const API_TOKEN = __ENV.API_TOKEN;

export default function () {
  const payload = JSON.stringify({
    name: 'Test User',
    email: `user-${__VU}-${__ITER}@example.com`,
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_TOKEN}`,
    },
  };

  const res = http.post(`${BASE_URL}/api/users`, payload, params);

  check(res, {
    'created successfully': (r) => r.status === 201,
    'has user id': (r) => JSON.parse(r.body).id !== undefined,
  });
}

Pass environment variables with -e:

k6 run -e BASE_URL=https://staging.api.com -e API_TOKEN=xxx load-test.js

Running in CI

k6 fits naturally into GitHub Actions:

- name: Run load test
  run: k6 run --out json=results.json load-tests/api-load-test.js
  env:
    BASE_URL: ${{ secrets.STAGING_API_URL }}
    API_TOKEN: ${{ secrets.API_TOKEN }}

- name: Upload results
  uses: actions/upload-artifact@v3
  with:
    name: k6-results
    path: results.json

The JSON output can be fed into a performance tracking dashboard or compared against a baseline from a previous run.

Grafana k6 Cloud

The open-source version runs tests from wherever you invoke it — your local machine, a CI runner, a server. For sustained or high-concurrency tests, you’re limited by the resources of your runner.

Grafana k6 Cloud manages test execution on distributed cloud infrastructure, provides built-in dashboards, stores historical results, and supports testing from multiple geographic regions simultaneously. It’s useful when you need to simulate more users than a single machine can generate, or when you want persistent performance trend data without building your own storage and dashboards.

The free tier includes 50 cloud test runs per month, which is enough for teams running load tests on each significant deployment.

What to Test

Start simple: the critical paths of your application under expected load, with thresholds based on your SLAs or user experience targets (e.g., 95th percentile response time under 300ms).

Once the basics are in place, add:

  • Spike tests: sudden traffic increase to model a marketing push or viral moment
  • Soak tests: sustained load over hours to find memory leaks and slow degradation
  • Breakpoint tests: gradually increase load until the system breaks, to understand actual capacity limits

The tests that find regressions in CI are short (1–5 minutes) and targeted at specific endpoints. The deeper stress and soak tests run less frequently — before a major release, or quarterly as a baseline check.

k6 puts all of this in the same workflow as your other tests. The tooling shouldn’t be the reason you’re not doing it.