k6 is a modern, open-source load testing tool that lets you script load tests in JavaScript. Running k6 within Docker containers is a common and powerful pattern, offering isolation, reproducibility, and scalability.
Let’s see k6 in action with a simple Docker Compose setup. Imagine we have a web service we want to test, running on http://my-web-app:8080. We’ll use k6 to send 100 virtual users for 30 seconds, hitting the /health endpoint.
First, create a k6-script.js file:
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 100,
duration: '30s',
};
export default function () {
http.get('http://my-web-app:8080/health');
sleep(1);
}
Next, create a docker-compose.yml file to orchestrate our test:
version: '3.8'
services:
k6-test:
image: loadimpact/k6
volumes:
- ./k6-script.js:/scripts/k6-script.js
command: ["run", "/scripts/k6-script.js"]
depends_on:
- my-web-app
my-web-app:
image: nginx:alpine # Replace with your actual web app image
ports:
- "8080:80"
To run this, navigate to the directory containing these files in your terminal and execute:
docker compose up
You’ll see k6 starting up, executing the script, and then reporting the results. The depends_on clause ensures our web app starts before k6 attempts to connect. The volumes directive mounts our JavaScript test script into the k6 container, making it accessible at /scripts/k6-script.js. The command then tells the loadimpact/k6 Docker image what to do: run our script.
This setup gives you a self-contained, portable load testing environment. You can easily scale this by running multiple k6 containers, either by adjusting the k6-script.js vus (virtual users) and duration parameters or by using Docker Compose’s scaling capabilities (docker compose up --scale k6-test=5). Each k6 instance will independently execute the script, and you can aggregate results from multiple runs or use k6’s built-in features for distributed testing.
The core problem k6 solves is making load testing accessible and scriptable for developers. Traditionally, load testing involved complex, often GUI-driven tools that were hard to integrate into CI/CD pipelines. k6, by using JavaScript, allows you to define load scenarios just like you write application code. This means you can version control your tests, run them automatically, and get detailed performance metrics directly within your development workflow. The k6/http module provides a familiar API for making HTTP requests, while k6/sleep allows you to introduce pauses between iterations, simulating user think times.
The options object in the k6 script is where you define the execution parameters. vus controls the number of concurrent virtual users, and duration specifies how long the test should run. These are fundamental levers for shaping your load profile. Beyond these basics, k6 offers advanced options for ramp-up, gradual increases in load over time, and custom metrics that you can define to track specific application behaviors.
When you define a service in docker-compose.yml with image: loadimpact/k6, you’re pulling the official k6 Docker image. This image contains the k6 binary and all its dependencies. The volumes mapping is crucial: it allows your local test script to be directly used by the k6 binary inside the container without needing to rebuild the image. The command overrides the default entrypoint of the image, telling it to execute k6 run with your script’s path.
A key insight for effective k6 containerization is understanding network resolution. When k6 runs in a Docker container and needs to communicate with another service (like my-web-app), it relies on Docker’s internal networking. The service name in the script (http://my-web-app:8080) directly maps to the service name defined in your docker-compose.yml file. Docker Compose sets up a network for your services, allowing them to discover and communicate with each other using these service names. If you were running k6 outside of Docker Compose and needed to target a service running in a container, you’d typically use localhost and the published port, but within a Compose network, the service name is the correct and most robust way to reference other services.
The next step in mastering k6 in Docker is often setting up distributed load testing, where you run multiple k6 instances across different machines or containers to simulate a massive number of users beyond what a single host can handle.