TL;DR
- k6 is a modern, developer-friendly load testing tool — write tests in JavaScript
- Install with brew/apt/docker, write scripts, run from CLI
- Define thresholds for pass/fail criteria: response time, error rate
- Built-in metrics: http_req_duration, http_reqs, iterations, vus
- Integrates with CI/CD, Grafana Cloud, InfluxDB for dashboards
Best for: Developers, DevOps, teams wanting code-based performance tests Skip if: You need GUI-based test design or exotic protocols (use JMeter) Reading time: 15 minutes
Your team wants performance tests in CI/CD. JMeter XML files are hard to review in PRs. Tests need to run headlessly in containers.
k6 solves this. Tests are JavaScript code — readable, versionable, reviewable. Runs from CLI, integrates with everything, reports in real-time.
This tutorial teaches k6 from installation to CI/CD integration.
What is k6?
k6 is an open-source load testing tool built for modern development workflows. Tests are JavaScript, execution is Go (fast), and results integrate with observability tools.
Why k6:
- JavaScript tests — use a real language, not XML
- Developer experience — great CLI, clear output
- High performance — single machine handles thousands of VUs
- CI/CD native — runs in containers, outputs machine-readable results
- Extensible — JavaScript APIs, extensions, protocols
Installation
# macOS
brew install k6
# Ubuntu/Debian
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
--keyserver hkp://keyserver.ubuntu.com:80 --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 update && sudo apt install k6
# Docker
docker run --rm -i grafana/k6 run - <script.js
# Windows
choco install k6
Verify installation:
k6 version
Your First k6 Test
Create script.js:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10, // 10 virtual users
duration: '30s', // run for 30 seconds
};
export default function () {
const res = http.get('https://test.k6.io');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
Run it:
k6 run script.js
k6 Concepts
Virtual Users (VUs)
VUs are parallel executors running your test function.
export const options = {
vus: 100, // concurrent users
duration: '5m', // test duration
};
Stages (Ramp-up/down)
Gradually increase and decrease load.
export const options = {
stages: [
{ duration: '2m', target: 100 }, // ramp to 100 users
{ duration: '5m', target: 100 }, // stay at 100
{ duration: '2m', target: 200 }, // ramp to 200
{ duration: '5m', target: 200 }, // stay at 200
{ duration: '2m', target: 0 }, // ramp down
],
};
Thresholds
Define pass/fail criteria.
export const options = {
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests < 500ms
http_req_failed: ['rate<0.01'], // error rate < 1%
checks: ['rate>0.99'], // 99% of checks pass
http_reqs: ['rate>100'], // at least 100 RPS
},
};
HTTP Requests
GET Request
import http from 'k6/http';
export default function () {
// Simple GET
const res = http.get('https://api.example.com/users');
// GET with parameters
const res2 = http.get('https://api.example.com/users?page=1&limit=10');
// GET with headers
const res3 = http.get('https://api.example.com/users', {
headers: {
'Authorization': 'Bearer token123',
'Accept': 'application/json',
},
});
}
POST Request
import http from 'k6/http';
export default function () {
const payload = JSON.stringify({
name: 'John Doe',
email: 'john@example.com',
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
const res = http.post('https://api.example.com/users', payload, params);
}
Other Methods
// PUT
http.put(url, payload, params);
// PATCH
http.patch(url, payload, params);
// DELETE
http.del(url, params);
// Batch requests (parallel)
const responses = http.batch([
['GET', 'https://api.example.com/users'],
['GET', 'https://api.example.com/products'],
['GET', 'https://api.example.com/orders'],
]);
Checks and Assertions
import { check } from 'k6';
export default function () {
const res = http.get('https://api.example.com/users');
check(res, {
'status is 200': (r) => r.status === 200,
'response has users': (r) => r.json().length > 0,
'response time OK': (r) => r.timings.duration < 500,
'content-type is JSON': (r) =>
r.headers['Content-Type'].includes('application/json'),
});
}
Test Data
Environment Variables
// script.js
const baseUrl = __ENV.BASE_URL || 'https://test.k6.io';
export default function () {
http.get(`${baseUrl}/api/users`);
}
k6 run -e BASE_URL=https://staging.example.com script.js
JSON Data File
// data.json
// [{"username": "user1", "password": "pass1"}, ...]
import { SharedArray } from 'k6/data';
const users = new SharedArray('users', function () {
return JSON.parse(open('./data.json'));
});
export default function () {
const user = users[Math.floor(Math.random() * users.length)];
// use user.username, user.password
}
CSV Data
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { SharedArray } from 'k6/data';
const csvData = new SharedArray('users', function () {
return papaparse.parse(open('./users.csv'), { header: true }).data;
});
Scenarios
Advanced execution patterns.
export const options = {
scenarios: {
// Constant load
constant_load: {
executor: 'constant-vus',
vus: 50,
duration: '5m',
},
// Ramping load
ramping_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 100 },
{ duration: '2m', target: 0 },
],
},
// Constant arrival rate
constant_rate: {
executor: 'constant-arrival-rate',
rate: 100, // 100 iterations per timeUnit
timeUnit: '1s', // per second
duration: '5m',
preAllocatedVUs: 50,
},
// Spike test
spike: {
executor: 'ramping-arrival-rate',
startRate: 10,
timeUnit: '1s',
stages: [
{ duration: '10s', target: 10 },
{ duration: '1m', target: 1000 }, // spike!
{ duration: '10s', target: 10 },
],
preAllocatedVUs: 500,
},
},
};
Metrics and Output
Built-in Metrics
| Metric | Description |
|---|---|
| http_reqs | Total HTTP requests |
| http_req_duration | Request duration |
| http_req_failed | Failed request rate |
| iterations | Completed iterations |
| vus | Current virtual users |
| data_received | Data received |
| data_sent | Data sent |
Custom Metrics
import { Counter, Trend, Rate, Gauge } from 'k6/metrics';
const orderCounter = new Counter('orders_created');
const orderDuration = new Trend('order_duration');
const orderSuccess = new Rate('order_success_rate');
const activeOrders = new Gauge('active_orders');
export default function () {
const start = Date.now();
const res = http.post('https://api.example.com/orders', orderPayload);
orderCounter.add(1);
orderDuration.add(Date.now() - start);
orderSuccess.add(res.status === 201);
activeOrders.add(10);
}
JSON Output
k6 run --out json=results.json script.js
InfluxDB + Grafana
k6 run --out influxdb=http://localhost:8086/k6 script.js
CI/CD Integration
GitHub Actions
name: Load Test
on:
push:
branches: [main]
jobs:
k6-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run k6 test
uses: grafana/k6-action@v0.3.1
with:
filename: tests/load-test.js
flags: --out json=results.json
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: k6-results
path: results.json
Docker
FROM grafana/k6
COPY ./tests /tests
ENTRYPOINT ["k6", "run", "/tests/script.js"]
docker build -t my-k6-tests .
docker run my-k6-tests
k6 with AI Assistance
AI tools can help write and optimize k6 tests.
What AI does well:
- Generate test scripts from API specs
- Create realistic test data
- Suggest threshold values
- Explain metrics and results
What still needs humans:
- Understanding system architecture
- Setting realistic load targets
- Interpreting results in context
- Debugging failures
FAQ
What is k6?
k6 is a modern, open-source load testing tool designed for developers. You write tests in JavaScript, run them from the command line, and get detailed performance metrics. Built by Grafana Labs, k6 focuses on developer experience, CI/CD integration, and high performance — a single machine can simulate thousands of virtual users.
Is k6 free?
Yes, k6 open-source (k6 OSS) is completely free. Grafana Cloud k6 offers hosted test execution, cloud storage, and additional features with both free and paid tiers. For most use cases, the open-source version provides everything you need.
k6 vs JMeter — which is better?
k6 is better for developers and CI/CD workflows — tests are code (JavaScript), execution is fast, and it integrates well with modern tooling. JMeter is better for GUI-based test design and supports more protocols out of the box. k6 is generally easier to learn and maintain for teams familiar with JavaScript.
Can k6 test browser interactions?
Yes, k6 has an experimental browser module that enables real browser testing using Chromium. However, k6’s strength is protocol-level testing (HTTP, WebSocket, gRPC) which is much more efficient for load testing. Use the browser module when you need to test JavaScript-rendered pages or complex browser interactions.
Official Resources
See Also
- JMeter Tutorial - Traditional GUI-based load testing
- Gatling Tutorial - Scala-based high-performance testing
- API Testing Guide - Complete API testing fundamentals
- Performance Testing Basics - Performance testing strategies
