---
title: "Smoke, Load, and Stress in CI: A Minimal Pipeline That Still Catches Regressions"
description: "Smoke, load, stress in CI: minimal k6 pipeline with thresholds—k6.io test types plus fast merge gates vs scheduled suites."
publishDate: "2026-07-27"
draft: false
keyword: "performance testing in ci"
intent: "MOFU"
cta: "Use Performate to turn this playbook into runnable k6 scenarios, thresholds, and shareable reports without losing days to glue code."
tags: ["k6","load-testing","api-performance","performance"]
---

Running a three-hour stress suite on every PR trains teams to disable the pipeline. **Minimal CI perf** means: fast smoke on merge, scheduled load, optional manual stress—same k6 repo, different `options` via env ([k6 test types](https://k6.io/docs/test-types/)). Gates must be fast enough to stay enabled and meaningful enough to catch regressions that unit tests miss.

This guide layers smoke, load, and stress by frequency, gives one script with a `TEST_PROFILE` switch, and documents what belongs in merge gates vs nightly jobs vs pre-launch rituals. Desktop tuning ([Postman → k6 workflow](/en/blog/postman-k6-reports-desktop-first-workflow)) should happen before CI carries the script.

## Layering gates: frequency vs risk

| Layer | Duration | Blocks merge? | Typical trigger |
|:---|:---|:---|:---|
| Smoke | 1–3 min | Yes | Every PR / merge to main |
| Load | 10–20 min | Nightly / release branch | Scheduled + pre-release |
| Stress | 30+ min | Weekly / manual | Infra change, launch rehearsal |

Smoke proves wiring, auth, and catastrophic latency regression at low RPS. Load validates SLO-shaped traffic. Stress maps breaking points—never daily on shared staging without approval.

### Why one repo beats three

Duplicated scripts drift within two sprints. One export with profile env var keeps thresholds and request definitions aligned—Performate desktop validates the same file CI runs.

## k6: one script, `TEST_PROFILE` switch

**Illustrative—not production-ready.** CI sets `TEST_PROFILE=smoke` on PR; nightly cron sets `load`.

**What this demonstrates:**

- Single `profiles` map—smoke/load/stress differ by rate, duration, thresholds.
- Tags `profile` on metrics for filtering nightly vs PR runs in Grafana.
- Think time scales with profile—smoke stays fast; load more realistic.
- Fail build on thresholds, not only checks—k6 exits non-zero on threshold breach.

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

const profile = __ENV.TEST_PROFILE || 'smoke';

const profiles = {
  smoke: {
    rate: 2,
    duration: '1m',
    thresholds: {
      http_req_failed: ['rate<0.01'],
      'http_req_duration{profile:smoke}': ['p(95)<800'],
    },
  },
  load: {
    rate: 25,
    duration: '10m',
    thresholds: {
      'http_req_duration{profile:load}': ['p(95)<600', 'p(99)<900'],
      http_req_failed: ['rate<0.01'],
    },
  },
  stress: {
    rate: 60,
    duration: '15m',
    thresholds: {
      http_req_failed: ['rate<0.05'],
    },
  },
};

const p = profiles[profile] || profiles.smoke;

export const options = {
  scenarios: {
    main: {
      executor: 'constant-arrival-rate',
      rate: p.rate,
      timeUnit: '1s',
      duration: p.duration,
      preAllocatedVUs: 20,
      maxVUs: 100,
      tags: { profile },
    },
  },
  thresholds: p.thresholds,
};

export default function () {
  const res = http.get(`${__ENV.API_BASE}/api/core`, { tags: { route: 'core', profile } });
  check(res, { ok: (r) => r.status < 500 });
  sleep(profile === 'smoke' ? 0.1 : 0.4);
}
```

**Patterns that work**

- **Tune load profile on desktop** until thresholds stable three runs—then promote to nightly CI.
- **Archive JSON summary** per release with `profile` tag—[quarterly review](/en/blog/quarterly-k6-scenarios-health-check) compares evidence.
- **Smoke uses staging secrets** via CI secret store ([k6 secrets](/en/blog/k6-secrets-test-environments))—never inline in repo.
- **Stress manual workflow** with runbook link—on-call approves window.

**Anti-patterns to avoid**

- Stress on every push—pipeline disabled within a month.
- Smoke without thresholds—passes while `p95` doubled.
- Different scripts for CI vs local—"works on my laptop" returns.
- Running load against production from CI without change control.

**Pro tip (example command):** local profile switch mirrors CI.

```bash
k6 run ci-profiles.js --env TEST_PROFILE=smoke --env API_BASE=https://staging.example.com
```

**What this command demonstrates:** developers reproduce CI failure in one command before pushing threshold tweaks—reduces merge-queue noise.

## Decision table: which profile when

| Event | Profile | Blocks deploy? |
|:---|:---|:---|
| PR opened | smoke | Yes, if on critical path |
| Nightly main | load | Alert, optional block |
| Pre-launch week | stress (manual) | Advisory unless SLO committee says block |
| Hotfix branch | smoke minimum | Yes |
| Infra resize | load + optional stress | Yes on staging gate |

**Keep smoke under three minutes**—includes checkout, install k6, run, upload artifact if possible.

**Schedule load** when staging is quiet—not concurrent with QA full regression without coordination.

## Observability and CI checklist

- [ ] Smoke uses staging secrets ([k6 secrets](/en/blog/k6-secrets-test-environments))—rotated independently of repo.
- [ ] Fail build on thresholds, not only checks—configure CI to respect k6 exit code.
- [ ] Archive JSON summary for [quarterly review](/en/blog/quarterly-k6-scenarios-health-check).
- [ ] Document `TEST_PROFILE` values in README—on-call reproduces nightly failure.
- [ ] Link CI smoke to same commit desktop export validated.
- [ ] Alert on load profile threshold miss separately from smoke—different response playbooks.

## How Performate supports minimal CI perf

Below is a **concrete workflow example** for promoting desktop-tuned script to layered CI—adapt rates to your SLO doc.

**Example: desktop tune → smoke gate → nightly load**

1. **Tune** load profile on desktop until `p95` stable—10m at business RPS. *Problem solved:* CI does not become your debugger.
2. **Export** script with `TEST_PROFILE` switch intact. *Problem solved:* one file, three gates—no drift.
3. **Wire** smoke in CI on PR—`TEST_PROFILE=smoke`, 1–3 min. *Problem solved:* merge blocked on real regression, not unit test gap alone.
4. **Schedule** load nightly on main—same script, different env. *Problem solved:* SLO-shaped signal without PR latency tax.
5. **Document** stress as manual workflow with Performate template saved. *Problem solved:* launch rehearsal exists without daily cost.
6. **Compare** reports release over release in comparison view. *Problem solved:* trends visible to release managers, not only engineers.

That workflow maps directly to the `cta` in this post: turn playbook into runnable scenarios without glue-code sprints.

## Closing takeaway

CI perf wins with **smoke every merge, load on a schedule**—not stress on every push. Export one profile-aware script from desktop, enable smoke gate this week, and schedule load before the next release train.

Merge the smoke job before adding load—teams that enable both at once often disable both when staging queues back up.

Document the `TEST_PROFILE` values in your service README and link the nightly load job in the same doc—on-call should not grep CI YAML at 2 AM to reproduce a red threshold.

When smoke fails on a hotfix branch, rerun locally with the same env profile before disabling the gate—most failures are stale secrets or wrong `API_BASE`, not perf regressions.

[Try Performate free](https://performate.app) | [CI/CD load testing](/en/blog/load-testing-in-ci-cd) | [Book a demo](/demo)
