<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Performance Testing Solutions</title>
    <link>https://dwwhalen.writeas.com/</link>
    <description></description>
    <pubDate>Mon, 17 Aug 2026 11:48:28 +0000</pubDate>
    <item>
      <title>Introduction</title>
      <link>https://dwwhalen.writeas.com/introduction?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[&#xA;Introduction&#xA;Load testing is crucial for ensuring your applications can handle expected load volumes. In this guide, we&#39;ll set up a complete load testing environment using k6 for testing, Prometheus for metrics collection, and Grafana for visualization—all orchestrated with Docker.&#xA;&#xA;Although there are paid versions of these products, this guide will focus exclusively on a basic setup with their open source Docker images. &#xA;&#xA;Prerequisites&#xA;Docker and Docker Compose installed&#xA;Basic understanding of load testing concepts&#xA;Familiarity with Docker&#xA;&#xA;Architecture Overview&#xA;Our setup consists of four main components:&#xA;&#xA;k6: Executes load tests and exports metrics&#xA;Application: A simple API-based application to test&#xA;Prometheus: Collects and stores metrics from k6&#xA;Grafana: Visualizes metrics from Prometheus&#xA;&#xA;These components will be implemented with 4 Docker containers. Here&#39;s how these components interact:&#xA;&#xA;k6 architecture diagram&#xA;&#xA;Data Flow:&#xA;Load generation: our k6 script sends HTTP requests to the Sample API to simulate user traffic&#xA;Metrics Export: as the test runs, performance metrics from k6 are exported to Prometheus via remote write&#xA;Data Query: Grafana uses PromQL to query Prometheus for metrics&#xA;&#xA;All components run within the same Docker network, enabling seamless communication between services.&#xA;&#xA;Project Structure&#xA;&#xA;k6-prometheus-grafana/&#xA;├── docker-compose.yml&#xA;├── prometheus/&#xA;│   └── prometheus.yml&#xA;├── grafana/&#xA;│   └── dashboards/&#xA;│       └── k6-dashboard.json&#xA;├── k6/&#xA;│   └── script.js&#xA;└── sample-api/&#xA;    └── Dockerfile&#xA;    └── server.js&#xA;&#xA;Step 1: Create the Sample API&#xA;&#xA;First, let&#39;s create a simple Node.js API to test against:&#xA;&#xA;sample-api/server.js&#xA;const express = require(&#39;express&#39;);&#xA;const app = express();&#xA;&#xA;app.get(&#39;/health&#39;, (req, res) =  {&#xA;  res.json({ status: &#39;healthy&#39;, timestamp: new Date().toISOString() });&#xA;});&#xA;&#xA;app.get(&#39;/api/users/:id&#39;, (req, res) =  {&#xA;  const { id } = req.params;&#xA;  // Simulate some processing delay&#xA;  setTimeout(() =  {&#xA;    res.json({ id, name: User ${id}, timestamp: new Date().toISOString() });&#xA;  }, Math.random()  100);&#xA;});&#xA;&#xA;app.post(&#39;/api/users&#39;, (req, res) =  {&#xA;  // Simulate user creation&#xA;  setTimeout(() =  {&#xA;    res.status(201).json({ &#xA;      id: Math.floor(Math.random()  1000),&#xA;      message: &#39;User created successfully&#39; &#xA;    });&#xA;  }, Math.random()  200);&#xA;});&#xA;&#xA;app.listen(3000, () =  {&#xA;  console.log(&#39;Server running on port 3000&#39;);&#xA;});&#xA;&#xA;And add a docker file that will start the app: &#xA;&#xA;sample-api/Dockerfile&#xA;FROM node:16-alpine&#xA;WORKDIR /app&#xA;RUN npm init -y &amp;&amp; npm install express&#xA;COPY . .&#xA;EXPOSE 3000&#xA;CMD [&#34;node&#34;, &#34;server.js&#34;]&#xA;&#xA;Step 2: Create k6 Test Script&#xA;&#xA;This JavaScript test script defines how k6 will interact with our sample API during the load test.&#xA;&#xA;k6/script.js&#xA;import http from &#39;k6/http&#39;;&#xA;import { check, sleep } from &#39;k6&#39;;&#xA;import { Rate, Counter, Trend } from &#39;k6/metrics&#39;;&#xA;&#xA;// Custom metrics - these allow us to track specific aspects of our test&#xA;export const errorRate = new Rate(&#39;errors&#39;);           // Tracks percentage of errors&#xA;export const myCounter = new Counter(&#39;mycounter&#39;);    // Simple incrementing counter&#xA;export const responseTime = new Trend(&#39;responsetime&#39;); // Tracks response time distribution&#xA;&#xA;export const options = {&#xA;  stages: [&#xA;    { duration: &#39;30s&#39;, target: 5 }, // Ramp up to 5 virtual users over 30 seconds&#xA;    { duration: &#39;90s&#39;, target: 20 }, // Ramp to from 5 to 20 virtual users over 90 seconds&#xA;    { duration: &#39;3m&#39;, target: 20 }, // Stay at 20 virtual users for 3 minutes&#xA;    { duration: &#39;30s&#39;, target: 0 },  // Gradually ramp down to 0 over 30 seconds&#xA;  ],&#xA;  thresholds: {&#xA;    httpreqduration: [&#39;p(95)&lt;500&#39;], // 95% of requests must complete in less than 500ms for the test to pass&#xA;    httpreqfailed: [&#39;rate&lt;0.1&#39;],    // Test fails if more than 10% of requests fail&#xA;  },&#xA;};&#xA;&#xA;export default function () {&#xA;  const baseUrl = &#39;http://sample-api:3000&#39;;&#xA;  &#xA;  // Test GET endpoint - fetches a random user&#xA;  let getResponse = http.get(${baseUrl}/api/users/${Math.floor(Math.random()  100)});&#xA;  check(getResponse, {&#xA;    &#39;GET status is 200&#39;: (r) =  r.status === 200,&#xA;    &#39;GET response time  500ms&#39;: (r) = r.timings.duration &lt; 500,&#xA;  });&#xA;  &#xA;  // Track custom metrics for this request&#xA;  errorRate.add(getResponse.status !== 200);&#xA;  responseTime.add(getResponse.timings.duration);&#xA;  myCounter.add(1);&#xA;  &#xA;  sleep(1); // Pause for 1 second between requests&#xA;  &#xA;  // Test POST endpoint - creates a new user&#xA;  let postResponse = http.post(${baseUrl}/api/users, JSON.stringify({&#xA;    name: TestUser${Date.now()},&#xA;    email: test${Date.now()}@example.com&#xA;  }), {&#xA;    headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },&#xA;  });&#xA;  &#xA;  check(postResponse, {&#xA;    &#39;POST status is 201&#39;: (r) =  r.status === 201,&#xA;    &#39;POST response time  1000ms&#39;: (r) = r.timings.duration &lt; 1000,&#xA;  });&#xA;  &#xA;  errorRate.add(postResponse.status !== 201);&#xA;  myCounter.add(1);&#xA;  &#xA;  sleep(1);&#xA;}&#xA;&#xA;Step 3: Configure Prometheus&#xA;&#xA;Prometheus is an open-source monitoring and alerting toolkit that collects and stores time-series metrics. The configuration below sets up Prometheus to scrape metrics from both itself and the k6 load testing tool.&#xA;&#xA;prometheus/prometheus.yml&#xA;global:&#xA;  scrapeinterval: 15s      # How frequently to scrape targets by default&#xA;  evaluationinterval: 15s  # How frequently to evaluate rules&#xA;scrapeconfigs:&#xA;  jobname: &#39;prometheus&#39;  # Self-monitoring configuration&#xA;    staticconfigs:&#xA;      targets: [&#39;localhost:9090&#39;]  # Prometheus&#39;s own metrics endpoint&#xA;  jobname: &#39;k6&#39;          # Configuration to scrape k6 metrics&#xA;    staticconfigs:&#xA;      targets: [&#39;k6:6565&#39;]  # k6&#39;s metrics endpoint (using Docker service name)&#xA;    scrapeinterval: 5s     # More frequent scraping for k6 during tests&#xA;    metricspath: /metrics  # Path where metrics are exposed&#xA;&#xA;Once Prometheus is collecting metrics, we&#39;ll be able to query this data directly or visualize it through Grafana in the next steps.&#xA;&#xA;Step 4: Grafana Dashboard Configuration&#xA;&#xA;Create a dashboard provisioning file for automatic setup:&#xA;&#xA;grafana/dashboards/dashboard.yml&#xA;apiVersion: 1&#xA;&#xA;providers:&#xA;  name: &#39;default&#39;&#xA;    orgId: 1&#xA;    folder: &#39;&#39;&#xA;    type: file&#xA;    disableDeletion: false&#xA;    editable: true&#xA;    options:&#xA;      path: /etc/grafana/provisioning/dashboards&#xA;&#xA;Step 5: Docker Compose Configuration&#xA;&#xA;docker-compose.yml&#xA;services:&#xA;  # Sample API service to be load tested by k6&#xA;  sample-api:&#xA;    build: ./sample-api&#xA;    ports:&#xA;      &#34;3000:3000&#34; # Exposes API on localhost:3000&#xA;    networks:&#xA;      k6-net&#xA;&#xA;  # Prometheus for metrics collection&#xA;  prometheus:&#xA;    image: prom/prometheus:latest&#xA;    containername: prometheus&#xA;    ports:&#xA;      &#34;9090:9090&#34; # Prometheus UI available at localhost:9090&#xA;    volumes:&#xA;      ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml # Custom config&#xA;    command:&#xA;      &#39;--config.file=/etc/prometheus/prometheus.yml&#39;&#xA;      &#39;--storage.tsdb.path=/prometheus&#39;&#xA;      &#39;--web.console.libraries=/etc/prometheus/consolelibraries&#39;&#xA;      &#39;--web.console.templates=/etc/prometheus/consoles&#39;&#xA;      &#39;--web.enable-lifecycle&#39; # Allows config reloads without restart&#xA;      &#39;--web.enable-remote-write-receiver&#39; # Enables remote write endpoint for k6&#xA;    networks:&#xA;      k6-net&#xA;&#xA;  # Grafana for dashboarding and visualization&#xA;  grafana:&#xA;    image: grafana/grafana:latest&#xA;    containername: grafana&#xA;    ports:&#xA;      &#34;3001:3000&#34; # Grafana UI available at localhost:3001&#xA;    environment:&#xA;      GFSECURITYADMINPASSWORD=admin # Default admin password&#xA;    volumes:&#xA;      grafana-storage:/var/lib/grafana # Persistent storage for Grafana data&#xA;      ./grafana/dashboards:/etc/grafana/provisioning/dashboards # Pre-provisioned dashboards&#xA;    networks:&#xA;      k6-net&#xA;    dependson:&#xA;      prometheus # Waits for Prometheus to be ready&#xA;&#xA;  # k6 load testing tool with Prometheus remote write output&#xA;  k6:&#xA;    image: grafana/k6:latest&#xA;    containername: k6&#xA;    ports:&#xA;      &#34;6565:6565&#34;&#xA;    environment:&#xA;      K6PROMETHEUSRWSERVERURL=http://prometheus:9090/api/v1/write # Prometheus remote write endpoint&#xA;      K6PROMETHEUSRWTRENDSTATS=p(95),p(99),min,max # Custom trend stats&#xA;    volumes:&#xA;      ./k6:/scripts # Mounts local k6 scripts&#xA;    command: run --out experimental-prometheus-rw /scripts/script.js # Runs the main k6 script&#xA;    networks:&#xA;      k6-net&#xA;    dependson:&#xA;      sample-api&#xA;      prometheus&#xA;&#xA;volumes:&#xA;  grafana-storage: # Named volume for Grafana data&#xA;&#xA;networks:&#xA;  k6-net:&#xA;    driver: bridge # Isolated network for all services&#xA;&#xA;Step 6: Start the stack&#xA;&#xA;Start all services:&#xA;docker-compose up -d&#xA;&#xA;Step 7: Setting Up a Pre-built K6 Dashboard&#xA;&#xA;Access Grafana: Navigate to http://localhost:3001&#xA;Login: Use admin/admin (you&#39;ll be prompted to change the password)&#xA;Add Prometheus Data Source First:&#xA;   Go to Configuration → Data Sources&#xA;   Click &#34;Add data source&#34;&#xA;   Select &#34;Prometheus&#34;&#xA;   Set URL to: http://prometheus:9090&#xA;   Click &#34;Save &amp; Test&#34;&#xA;Import K6 Dashboard:&#xA;   Click the &#34;+&#34; icon in the left sidebar&#xA;   Select &#34;Import&#34;&#xA;   Use one of these dashboard IDs for Prometheus:&#xA;     19665 - K6 Prometheus (recommended)&#xA;     10660 - K6 Load Testing Results (Prometheus)&#xA;     19634 - K6 Performance Test Dashboard&#xA;   Click &#34;Load&#34;&#xA;   Select your Prometheus data source&#xA;   Click &#34;Import&#34;&#xA;&#xA;Step 8: Run the load test&#xA;&#xA;Run the k6 test:&#xA;docker-compose run --rm k6 run --out experimental-prometheus-rw /scripts/script.js&#xA;As the test runs, k6 will send API requests to the sample API, and metrics will be collected and sent to Prometheus.  You can monitor the test progress in the terminal.&#xA;&#xA;Step 9: Monitor your test run in Grafana&#xA;Navigate to to the Grana http://localhost:3001, select your Dashboard from the left nav, and you can monitor your test real time with the Grafana dashboard, which should look something like this:&#xA;Grafana dashboard screenshot&#xA;&#xA;Cleanup&#xA;&#xA;Stop and remove all containers and volumes:&#xA;docker-compose down -v&#xA;&#xA;Conclusion&#xA;&#xA;The point of this post was just to provide awareness of the open source options available to you and you consider k6 for load testing.  I skimmed over a lot of detail and explanation about k6, Prometheus, and Grafana.  I will likely fill in some detail with future posts.  Until then, this setup provides a complete observability stack for K6 load testing.&#xA;&#xA;The Docker-based approach ensures consistency across environments and makes it easy to integrate into CI/CD pipelines. And FYI, you can fin all the code from this blog post here.&#xA;&#xA;Thanks for reading and let me know if you have any questions or suggestions for future posts!]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Load testing is crucial for ensuring your applications can handle expected load volumes. In this guide, we&#39;ll set up a complete load testing environment using k6 for testing, Prometheus for metrics collection, and Grafana for visualization—all orchestrated with Docker.</p>

<p>Although there are paid versions of these products, this guide will focus exclusively on a basic setup with their open source Docker images.</p>

<h2 id="prerequisites">Prerequisites</h2>
<ul><li>Docker and Docker Compose installed</li>
<li>Basic understanding of load testing concepts</li>
<li>Familiarity with Docker</li></ul>

<h2 id="architecture-overview">Architecture Overview</h2>

<p>Our setup consists of four main components:</p>

<p><strong>k6</strong>: Executes load tests and exports metrics
<strong>Application</strong>: A simple API-based application to test
<strong>Prometheus</strong>: Collects and stores metrics from k6
<strong>Grafana</strong>: Visualizes metrics from Prometheus</p>

<p>These components will be implemented with 4 Docker containers. Here&#39;s how these components interact:</p>

<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mw4ewsyvufil8p0umt43.png" alt="k6 architecture diagram"/></p>

<h3 id="data-flow">Data Flow:</h3>
<ol><li><strong>Load generation</strong>: our k6 script sends HTTP requests to the Sample API to simulate user traffic</li>
<li><strong>Metrics Export</strong>: as the test runs, performance metrics from k6 are exported to Prometheus via remote write</li>
<li><strong>Data Query</strong>: Grafana uses PromQL to query Prometheus for metrics</li></ol>

<p>All components run within the same Docker network, enabling seamless communication between services.</p>

<h2 id="project-structure">Project Structure</h2>

<pre><code>k6-prometheus-grafana/
├── docker-compose.yml
├── prometheus/
│   └── prometheus.yml
├── grafana/
│   └── dashboards/
│       └── k6-dashboard.json
├── k6/
│   └── script.js
└── sample-api/
    └── Dockerfile
    └── server.js
</code></pre>

<h2 id="step-1-create-the-sample-api">Step 1: Create the Sample API</h2>

<p>First, let&#39;s create a simple Node.js API to test against:</p>

<p><strong>sample-api/server.js</strong></p>

<pre><code class="language-javascript">const express = require(&#39;express&#39;);
const app = express();

app.get(&#39;/health&#39;, (req, res) =&gt; {
  res.json({ status: &#39;healthy&#39;, timestamp: new Date().toISOString() });
});

app.get(&#39;/api/users/:id&#39;, (req, res) =&gt; {
  const { id } = req.params;
  // Simulate some processing delay
  setTimeout(() =&gt; {
    res.json({ id, name: `User ${id}`, timestamp: new Date().toISOString() });
  }, Math.random() * 100);
});

app.post(&#39;/api/users&#39;, (req, res) =&gt; {
  // Simulate user creation
  setTimeout(() =&gt; {
    res.status(201).json({ 
      id: Math.floor(Math.random() * 1000),
      message: &#39;User created successfully&#39; 
    });
  }, Math.random() * 200);
});

app.listen(3000, () =&gt; {
  console.log(&#39;Server running on port 3000&#39;);
});
</code></pre>

<p>And add a docker file that will start the app:</p>

<p><strong>sample-api/Dockerfile</strong></p>

<pre><code class="language-dockerfile">FROM node:16-alpine
WORKDIR /app
RUN npm init -y &amp;&amp; npm install express
COPY . .
EXPOSE 3000
CMD [&#34;node&#34;, &#34;server.js&#34;]
</code></pre>

<h2 id="step-2-create-k6-test-script">Step 2: Create k6 Test Script</h2>

<p>This JavaScript test script defines how k6 will interact with our sample API during the load test.</p>

<p><strong>k6/script.js</strong></p>

<pre><code class="language-javascript">import http from &#39;k6/http&#39;;
import { check, sleep } from &#39;k6&#39;;
import { Rate, Counter, Trend } from &#39;k6/metrics&#39;;

// Custom metrics - these allow us to track specific aspects of our test
export const errorRate = new Rate(&#39;errors&#39;);           // Tracks percentage of errors
export const myCounter = new Counter(&#39;my_counter&#39;);    // Simple incrementing counter
export const responseTime = new Trend(&#39;response_time&#39;); // Tracks response time distribution

export const options = {
  stages: [
    { duration: &#39;30s&#39;, target: 5 }, // Ramp up to 5 virtual users over 30 seconds
    { duration: &#39;90s&#39;, target: 20 }, // Ramp to from 5 to 20 virtual users over 90 seconds
    { duration: &#39;3m&#39;, target: 20 }, // Stay at 20 virtual users for 3 minutes
    { duration: &#39;30s&#39;, target: 0 },  // Gradually ramp down to 0 over 30 seconds
  ],
  thresholds: {
    http_req_duration: [&#39;p(95)&lt;500&#39;], // 95% of requests must complete in less than 500ms for the test to pass
    http_req_failed: [&#39;rate&lt;0.1&#39;],    // Test fails if more than 10% of requests fail
  },
};

export default function () {
  const baseUrl = &#39;http://sample-api:3000&#39;;
  
  // Test GET endpoint - fetches a random user
  let getResponse = http.get(`${baseUrl}/api/users/${Math.floor(Math.random() * 100)}`);
  check(getResponse, {
    &#39;GET status is 200&#39;: (r) =&gt; r.status === 200,
    &#39;GET response time &lt; 500ms&#39;: (r) =&gt; r.timings.duration &lt; 500,
  });
  
  // Track custom metrics for this request
  errorRate.add(getResponse.status !== 200);
  responseTime.add(getResponse.timings.duration);
  myCounter.add(1);
  
  sleep(1); // Pause for 1 second between requests
  
  // Test POST endpoint - creates a new user
  let postResponse = http.post(`${baseUrl}/api/users`, JSON.stringify({
    name: `TestUser_${Date.now()}`,
    email: `test_${Date.now()}@example.com`
  }), {
    headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
  });
  
  check(postResponse, {
    &#39;POST status is 201&#39;: (r) =&gt; r.status === 201,
    &#39;POST response time &lt; 1000ms&#39;: (r) =&gt; r.timings.duration &lt; 1000,
  });
  
  errorRate.add(postResponse.status !== 201);
  myCounter.add(1);
  
  sleep(1);
}
</code></pre>

<h2 id="step-3-configure-prometheus">Step 3: Configure Prometheus</h2>

<p>Prometheus is an open-source monitoring and alerting toolkit that collects and stores time-series metrics. The configuration below sets up Prometheus to scrape metrics from both itself and the k6 load testing tool.</p>

<p><strong>prometheus/prometheus.yml</strong></p>

<pre><code class="language-yaml">global:
  scrape_interval: 15s      # How frequently to scrape targets by default
  evaluation_interval: 15s  # How frequently to evaluate rules
scrape_configs:
  - job_name: &#39;prometheus&#39;  # Self-monitoring configuration
    static_configs:
      - targets: [&#39;localhost:9090&#39;]  # Prometheus&#39;s own metrics endpoint
  - job_name: &#39;k6&#39;          # Configuration to scrape k6 metrics
    static_configs:
      - targets: [&#39;k6:6565&#39;]  # k6&#39;s metrics endpoint (using Docker service name)
    scrape_interval: 5s     # More frequent scraping for k6 during tests
    metrics_path: /metrics  # Path where metrics are exposed
</code></pre>

<p>Once Prometheus is collecting metrics, we&#39;ll be able to query this data directly or visualize it through Grafana in the next steps.</p>

<h2 id="step-4-grafana-dashboard-configuration">Step 4: Grafana Dashboard Configuration</h2>

<p>Create a dashboard provisioning file for automatic setup:</p>

<p><strong>grafana/dashboards/dashboard.yml</strong></p>

<pre><code class="language-yaml">apiVersion: 1

providers:
  - name: &#39;default&#39;
    orgId: 1
    folder: &#39;&#39;
    type: file
    disableDeletion: false
    editable: true
    options:
      path: /etc/grafana/provisioning/dashboards
</code></pre>

<h2 id="step-5-docker-compose-configuration">Step 5: Docker Compose Configuration</h2>

<p><strong>docker-compose.yml</strong></p>

<pre><code class="language-yaml">services:
  # Sample API service to be load tested by k6
  sample-api:
    build: ./sample-api
    ports:
      - &#34;3000:3000&#34; # Exposes API on localhost:3000
    networks:
      - k6-net

  # Prometheus for metrics collection
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - &#34;9090:9090&#34; # Prometheus UI available at localhost:9090
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml # Custom config
    command:
      - &#39;--config.file=/etc/prometheus/prometheus.yml&#39;
      - &#39;--storage.tsdb.path=/prometheus&#39;
      - &#39;--web.console.libraries=/etc/prometheus/console_libraries&#39;
      - &#39;--web.console.templates=/etc/prometheus/consoles&#39;
      - &#39;--web.enable-lifecycle&#39; # Allows config reloads without restart
      - &#39;--web.enable-remote-write-receiver&#39; # Enables remote write endpoint for k6
    networks:
      - k6-net

  # Grafana for dashboarding and visualization
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - &#34;3001:3000&#34; # Grafana UI available at localhost:3001
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin # Default admin password
    volumes:
      - grafana-storage:/var/lib/grafana # Persistent storage for Grafana data
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards # Pre-provisioned dashboards
    networks:
      - k6-net
    depends_on:
      - prometheus # Waits for Prometheus to be ready

  # k6 load testing tool with Prometheus remote write output
  k6:
    image: grafana/k6:latest
    container_name: k6
    ports:
      - &#34;6565:6565&#34;
    environment:
      - K6_PROMETHEUS_RW_SERVER_URL=http://prometheus:9090/api/v1/write # Prometheus remote write endpoint
      - K6_PROMETHEUS_RW_TREND_STATS=p(95),p(99),min,max # Custom trend stats
    volumes:
      - ./k6:/scripts # Mounts local k6 scripts
    command: run --out experimental-prometheus-rw /scripts/script.js # Runs the main k6 script
    networks:
      - k6-net
    depends_on:
      - sample-api
      - prometheus

volumes:
  grafana-storage: # Named volume for Grafana data

networks:
  k6-net:
    driver: bridge # Isolated network for all services

</code></pre>

<h2 id="step-6-start-the-stack">Step 6: Start the stack</h2>
<ol><li><strong>Start all services:</strong>
<code>bash
docker-compose up -d
</code></li></ol>

<h2 id="step-7-setting-up-a-pre-built-k6-dashboard">Step 7: Setting Up a Pre-built K6 Dashboard</h2>
<ol><li><strong>Access Grafana</strong>: Navigate to <a href="http://localhost:3001" rel="nofollow">http://localhost:3001</a></li>
<li><strong>Login</strong>: Use admin/admin (you&#39;ll be prompted to change the password)</li>
<li><strong>Add Prometheus Data Source First</strong>:
<ul><li>Go to Configuration → Data Sources</li>
<li>Click “Add data source”</li>
<li>Select “Prometheus”</li>
<li>Set URL to: <code>http://prometheus:9090</code></li>
<li>Click “Save &amp; Test”</li></ul></li>
<li><strong>Import K6 Dashboard</strong>:
<ul><li>Click the “+” icon in the left sidebar</li>
<li>Select “Import”</li>
<li>Use one of these dashboard IDs for Prometheus:
<ul><li><strong>19665</strong> – K6 Prometheus (recommended)</li>
<li><strong>10660</strong> – K6 Load Testing Results (Prometheus)</li>
<li><strong>19634</strong> – K6 Performance Test Dashboard</li></ul></li>
<li>Click “Load”</li>
<li>Select your Prometheus data source</li>
<li>Click “Import”</li></ul></li></ol>

<h2 id="step-8-run-the-load-test">Step 8: Run the load test</h2>
<ol><li><strong>Run the k6 test:</strong>
<code>bash
docker-compose run --rm k6 run --out experimental-prometheus-rw /scripts/script.js
</code>
As the test runs, k6 will send API requests to the sample API, and metrics will be collected and sent to Prometheus.  You can monitor the test progress in the terminal.</li></ol>

<h2 id="step-9-monitor-your-test-run-in-grafana">Step 9: Monitor your test run in Grafana</h2>

<p>Navigate to to the Grana <a href="http://localhost:3001" rel="nofollow">http://localhost:3001</a>, select your Dashboard from the left nav, and you can monitor your test real time with the Grafana dashboard, which should look something like this:
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qyzh6fbej5ta7zyjcr43.png" alt="Grafana dashboard screenshot"/></p>

<h2 id="cleanup">Cleanup</h2>

<p>Stop and remove all containers and volumes:</p>

<pre><code class="language-bash">docker-compose down -v
</code></pre>

<h2 id="conclusion">Conclusion</h2>

<p>The point of this post was just to provide awareness of the open source options available to you and you consider k6 for load testing.  I skimmed over a lot of detail and explanation about k6, Prometheus, and Grafana.  I will likely fill in some detail with future posts.  Until then, this setup provides a complete observability stack for K6 load testing.</p>

<p>The Docker-based approach ensures consistency across environments and makes it easy to integrate into CI/CD pipelines. And FYI, you can fin all the code from this blog post <a href="https://github.com/dwwhalen/k6-prometheus-grafana" rel="nofollow">here</a>.</p>

<p>Thanks for reading and let me know if you have any questions or suggestions for future posts!</p>
]]></content:encoded>
      <guid>https://dwwhalen.writeas.com/introduction</guid>
      <pubDate>Sun, 18 May 2025 23:16:15 +0000</pubDate>
    </item>
  </channel>
</rss>