TL;DR
Learn how to run Cypress tests in parallel using Jenkins CI, Docker, and Kubernetes.
There are plenty of guides out there showing how to run Cypress tests in parallel using CI/CD tools like GitHub Actions, GitLab CI, CircleCI, and Jenkins.
But what if I told you that you can take it one step further—and integrate Kubernetes?
Yes, you read that right. You can run your tests across multiple pods to massively speed up execution using K8s.
🔧 Tools Used
- Cypress Dashboard (for parallelization logic).
- Jenkins CI (or any similar CI like GitHub Actions).
- Docker (to pin Node/Chrome versions).
- Kubernetes (to run multiple Cypress pods).
Project structure
│cypress/
│ └── e2e/
│ └── testsuite1.spec.js
│ └── ……
├── cypress.config.js
├── Dockerfile
├── Jenkinsfile
└── k8s/
└── cypress-job.yaml
Step 1: Cypress setup with parallelization
- Create a basic Cypress project on Cypress Cloud to enable parallelization logic
- Grab your project ID and record key.
- Follow Cypress Dashboard rules to connect your Cypress framework to the Dashboard by explicitly defining project ID in your config file and adding cypress key to your CLI command that run test execution
cypress.config.js
const { defineConfig } = require("cypress");
module.exports = defineConfig({
// Your projectId from Cypress Dashboard project settings:
projectId: "xyz123",
e2e: {
……
},
});
Your CLI command will look something like this
npx cypress run --headless --record --key ${{your-secure-record-key-from-Cypress-Dashboard}} --parallel --group regression --ci-build-id ${{BUILD_NUMBER_GENERATED_BY_CI_PROVIDER}} —-env url=${{test-env-variable}}
I use additional flag —-env url=
since I run tests on multiple environments. But you can exclude that.
Step 2: Create a Dockerfile
I normally use original Dockerfile from Cypress itself.
dockerfile
# Use Cypress included image matching package.json version
FROM cypress/included:14.0.1
# Set working directory to root (where config files reside)
WORKDIR /
# Copy package files and all config files to root
COPY package.json package-lock.json cypress.*.config.js ./
# Install dependencies and ensure Cypress binary is downloaded
RUN npm ci && \
npx cypress install --force && \
ls -l /root/.cache/Cypress/14.0.1/Cypress/Cypress || echo "Binary install failed"
# Copy the rest of the project files
COPY . .
# Default environment variables (can be overridden)
ARG ENVIRONMENT=stage
ARG CYPRESS_KEY
ARG TEST_FILE=""
ENV CYPRESS_ENVIRONMENT=$ENVIRONMENT
ENV CYPRESS_RECORD_KEY=$CYPRESS_KEY
ENV TEST_FILE=$TEST_FILE
# Default command (will be overridden in pipeline)
CMD ["npx", "cypress", "run", "--headless", "--record", "--key", "$CYPRESS_RECORD_KEY", "--env", "url=$CYPRESS_ENVIRONMENT"]
Step 3: Create a Jenkinsfile
In my case I re-use common template that has been made for each and every repo CI pipeline on our product, so I just needed to refer to shared lib.
But you follow your own company policies. The Jenkins template would be
groovy
pipeline {
agent any
environment {
CYPRESS_RECORD_KEY = credentials('cypress-key')
BUILD_ID = "${env.BUILD_NUMBER}"
}
stages {
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Run Cypress Tests in Parallel') {
steps {
script {
def environmentToUse = params.ENV_NAME ?: 'stage'
def keyToUse = environmentToUse == 'stage' ? cypressKey : cypressKeyDev
def configFile = "cypress.${environmentToUse}.config.js"
echo "Using Cypress config file: ${configFile} for environment: ${environmentToUse}"
def testFiles = sh(script: """#!/bin/sh
find cypress/e2e -name '*.spec.js' 2>/dev/null || echo "No test files found"
""", returnStdout: true).trim().split('\n').findAll { it && it != "No test files found" }
if (!testFiles) {
error "No Cypress test files (*.spec.js) found in cypress/e2e directory!"
}
echo "Found ${testFiles.size()} test files: ${testFiles.join(', ')}"
def numContainers = Math.min(testFiles.size(), 8)
def parallelTasks = [:]
for (int i = 0; i < numContainers; i++) {
def startIdx = (i * testFiles.size() / numContainers).toInteger()
def endIdx = ((i + 1) * testFiles.size() / numContainers).toInteger()
def subset = testFiles[startIdx..
Step 4: Define a Kubernetes Job
The most complicated step I would say.
Cypress test command should remain consistent between your Jenkinsfile and the Kubernetes Deployment or Job YAML file.
You may ask Why? Because Cypress uses the --record
, --parallel
, and --ci-build-id
flags to coordinate and split specs across machines (or pods in our case).
So:
- Jenkins is responsible for triggering the build and setting environment variables.
- Kubernetes is responsible for running the actual test command inside pods.
Step 4a: How to securely pass CI_BUILD_ID and CYPRESS_RECORD_KEY
You should not hard-code sensitive values in YAML files or source control.
Use Kubernetes secrets instead:
bash
kubectl create secret generic cypress-secrets \
--from-literal=CYPRESS_RECORD_KEY=your-record-key \
--from-literal=CI_BUILD_ID=your-ci-build-id
Reference them in your YAML file as environment variables
k8s/cypress-job.yaml
yaml
env:
- name: CYPRESS_RECORD_KEY
valueFrom:
secretKeyRef:
name: cypress-secrets
key: CYPRESS_RECORD_KEY
- name: CI_BUILD_ID
valueFrom:
secretKeyRef:
name: cypress-secrets
key: CI_BUILD_ID
In your test command inside the container, use the same npx cypress run ... as in Jenkins.
apiVersion: batch/v1
kind: Job
metadata:
name: cypress-parallel-job
spec:
parallelism: 3
completions: 3
template:
spec:
containers:
- name: cypress-runner
image: cypress/browsers:node-18.12.0-chrome-123.0.6312.86-1
command: ["/bin/sh", "-c"]
args:
- >
npm ci &&
npx cypress run \
--record \
--key $(CYPRESS_RECORD_KEY) \
--parallel \
--group "regression" \
--ci-build-id $(CI_BUILD_ID) \
--browser chrome
env:
- name: CYPRESS_RECORD_KEY
valueFrom:
secretKeyRef:
name: cypress-secrets
key: CYPRESS_RECORD_KEY
- name: CI_BUILD_ID
valueFrom:
secretKeyRef:
name: cypress-secrets
key: CI_BUILD_ID
restartPolicy: Never
parallelism: 3
- Defines how many pods run in parallel
completions: 3
- Defines how many successful completions are required for the job to be considered complete. Each successful pod counts as one completion.
Normally, Cypress Dashboard will show you how many machines are needed for the fastest execution
IMPORTANT: Each pod in this setup runs the same command, so to avoid duplicating test effort, you need Cypress’s --parallel and --ci-build-id logic to coordinate test distribution across those pods. They have to use same command and same secrets
Step 4b: 🚀 How Kubernetes runs multiple pods for parallel tests
When using --parallel
in Cypress and deploying tests via Kubernetes, each pod acts as one parallelized "machine" for Cypress Dashboard to split test specs.
You can use a Kubernetes Job or Deployment with a replicaCount
or control pod spawning using a custom script:
Option A: Static replicas (simpler)
spec:
replicas: 4 # Number of parallel test runners
Option B: Dynamic pods based on spec count (advanced)
You can write a pre-step script (in Jenkins or elsewhere) that:
- Counts the number of Cypress spec files.
- Decides how many pods are needed.
- Adjusts the Job YAML using
envsubst
orsed
, or a Helm chart. - Applies the modified YAML via
kubectl apply -f
.
Step 5: Managing memory & resource allocation
Use resources in YAML to allocate CPU and memory for each pod:
yaml
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
If Cypress tests are heavy:
- Start with 1Gi per pod and adjust based on OOM kills or slowness.
- Use Node Autoscaler to spin up new nodes if cluster can't handle the load.
- Or use Cluster Autoscaler on AWS EKS / GCP GKE / Azure AKS.
🛠️ Bonus: Auto-Scaling with a custom script
Use a script (Node.js, Bash, or Python) to:
This lets you scale parallel runs dynamically without hardcoding pod counts.
- Detect the number of
.spec.js
files. - Set a max pod count per node.
- Patch a YAML template using
envsubst
or a Helm chart. - Apply it to the cluster with
kubectl
.
Final Thoughts
Running Cypress tests in parallel isn’t just an optimization - it’s a game changer when it comes to test execution time and CI/CD scalability.
By combining:
- Cypress Cloud’s built-in parallelization logic
- Jenkins for CI orchestration
- Docker to lock down your test environment
- Kubernetes to scale execution across multiple pods
you get a powerful, maintainable, and cloud-native testing setup.
🔐 Secure parameter passing with Kubernetes Secrets
⚡ Dynamically spin up multiple pods for faster test runs
💡 Keep Cypress logic consistent across local, CI, and K8s
Whether you're scaling out your test suite, trying to reduce feedback loops, or modernizing your dev pipeline—this approach gives you the tools to move fast without breaking things.