GitHub Actions covers most projects, but plenty of teams run on Jenkins, GitLab CI, CircleCI, or self-hosted runners. The good news: the concepts are identical across every platform — install dependencies, build the app, run Cypress, save artifacts. Only the YAML/Groovy syntax changes. This lesson covers Jenkins and GitLab CI head-to-head, the Cypress Docker images that make any of them painless, and the gotchas that come up when you move off GitHub.
The Cypress Docker images — your shortcut
Every CI platform that supports Docker can use Cypress's official images. They ship with Node.js, Cypress, Chrome, Firefox, and Edge pre-installed. No browser provisioning, no apt-get install, no surprise binary mismatches.
cypress/included:<version>— Cypress + browsers + Node.js. Runcypress rundirectly. Best for CI.cypress/browsers:<version>— browsers + Node.js, no Cypress. Use when you install Cypress yourself.cypress/base:<version>— Node.js + system deps, no browsers. Smallest; rarely the right choice for CI.
For most CI configs, cypress/included is the only image you need. Pin the tag to a version (cypress/included:13.6.0) so an upstream Cypress release doesn't silently change your CI image overnight.
Jenkins — the canonical pipeline
Jenkins uses a Jenkinsfile checked into the repo. Declarative pipeline syntax is the modern default:
pipeline {
agent {
docker {
image 'cypress/included:13.6.0'
args '-u root' // run as root so npm install can write to node_modules
}
}
environment {
CYPRESS_BASE_URL = credentials('staging-base-url')
CYPRESS_ADMIN_PASSWORD = credentials('staging-admin-password')
}
stages {
stage('Install') {
steps { sh 'npm ci' }
}
stage('Test') {
steps { sh 'npx cypress run --browser chrome' }
}
stage('Report') {
steps { sh 'npm run cy:report' }
}
}
post {
always {
archiveArtifacts(
artifacts: 'cypress/screenshots/**, cypress/videos/**, cypress/reports/html/**',
allowEmptyArchive: true
)
junit allowEmptyResults: true, testResults: 'cypress/reports/junit/*.xml'
}
}
}The structure mirrors the GitHub Actions workflow:
agent { docker { image: '...' } }— every stage runs inside the Cypress image. No Jenkins-side browser install.environment { CYPRESS_X = credentials(...) }— Jenkins's credentials plugin injects secrets. Same pattern as GitHub'ssecretscontext.stages— install, test, report, in order.post { always { ... } }— runs whether the pipeline succeeded or failed.archiveArtifactsis Jenkins's equivalent of GitHub'supload-artifact.junitparses Mocha JUnit XML and feeds the Jenkins test-results dashboard.
If your team uses a multi-reporter setup that emits both Mochawesome JSON and JUnit XML (chapter 7), Jenkins gives you a native test-results page and a downloadable HTML report.
GitLab CI — concise YAML
GitLab CI uses .gitlab-ci.yml. Same structure, lighter syntax:
stages:
- test
cypress:
stage: test
image: cypress/included:13.6.0
variables:
CYPRESS_BASE_URL: $STAGING_URL
CYPRESS_ADMIN_PASSWORD: $STAGING_ADMIN_PASSWORD
script:
- npm ci
- npx cypress run --browser chrome
- npm run cy:report
artifacts:
when: always
paths:
- cypress/screenshots
- cypress/videos
- cypress/reports/html
reports:
junit: cypress/reports/junit/*.xml
expire_in: 1 weekGitLab's variables block reads from project-level CI/CD settings (the equivalent of GitHub Actions secrets). artifacts.reports.junit integrates with GitLab's own test-result dashboard. Everything compresses into roughly half the lines of the equivalent Jenkinsfile.
GitLab has the smoothest first-party container support of any CI — image: cypress/included is all you need; the runner pulls and caches it automatically.
The three platforms side by side
GitHub Actions vs Jenkins vs GitLab CI for Cypress
GitHub Actions
YAML in .github/workflows/
First-party cypress-io/github-action handles caching, browser, wait-on
Hosted runners with browsers preinstalled — no Docker needed
Best for: GitHub-hosted projects of any size
Jenkins
Groovy Jenkinsfile in repo root
Run inside cypress/included Docker image for clean browser env
credentials() plugin for secrets; junit step for test reports
Best for: enterprise environments with existing Jenkins infra
GitLab CI
YAML in .gitlab-ci.yml
Native image: cypress/included support
artifacts.reports.junit feeds the GitLab test dashboard
Best for: GitLab-hosted projects, especially self-managed instances
The trade-off across all three: GitHub Actions gives you the slickest first-party Cypress integration; Jenkins gives you the most flexibility (and the most YAML); GitLab sits in the middle on both axes. Pick whichever your repo is already on — none of the three is dramatically better than the others for Cypress specifically.
Common cross-platform issues
A few problems that come up on every non-GitHub platform until you've seen them once:
- "Missing browsers." The CI runner doesn't have Chrome installed. Fix: use
cypress/includedas the image. Don't try toapt-get install chromeon a base Linux image — the Cypress images encode the right Chrome version for the Cypress version. - "Cannot find display / X server." Cypress runs headless inside the Docker images via Xvfb (a virtual display). If you bypass the official images and run on a custom Linux box, you may need to install and start Xvfb manually. Stick with the official images and skip the headache.
- Out-of-memory crashes on long runs. Cypress is memory-hungry; the CI runner's default Node memory cap (~512 MB on some platforms) isn't enough. Increase with
NODE_OPTIONS: "--max-old-space-size=4096"in the env block. - Volume permission errors when running as root. Some Docker setups complain about file ownership when the container writes to a mounted volume. The Jenkinsfile above passes
-u rootto sidestep this; on GitLab, pinning a specific user in the image works similarly.
A real-world dual-platform setup
Some teams run both GitHub Actions and Jenkins — Actions for fast PR checks, Jenkins for nightly heavy regression on a self-hosted runner with database access. The Cypress side stays identical; only the orchestration changes. Keep cypress.config.ts, cypress.env.json, and your test code platform-agnostic. Each platform's config file is just a different way of saying "install, build, run, archive."
This is the single most useful framing for thinking about CI: the platform is wiring; the work is the same. Once you've set up Cypress on one platform, the second one takes 20 minutes.
⚠️ Common mistakes
- Installing Chrome manually instead of using a Cypress Docker image. Versions drift, missing system libs cause cryptic crashes, and the maintenance burden is yours forever. The Cypress team publishes new images for every release — let them carry the integration cost.
- Hardcoding secrets in the Jenkinsfile or
.gitlab-ci.yml. Anything checked in is leaked. Use the platform's secret store:credentials(...)in Jenkins, project-level CI/CD variables in GitLab. - Saving artifacts only on success or only on failure. The most useful artifact is the one from the failing run, but
when: alwaysensures the report exists for both. Stakeholders expect the same green dashboard URL whether the build passed or failed.
🎯 Practice task
Set up a Cypress CI run on a non-GitHub platform. 30-45 minutes.
- Pick GitLab CI (free public projects on gitlab.com) or a local Jenkins (a Docker
jenkins/jenkins:ltsinstance is a 5-minute setup). - GitLab path: push your project to a GitLab repo. Create
.gitlab-ci.ymlfrom the lesson template. DefineSTAGING_URLandSTAGING_ADMIN_PASSWORDas project CI variables. Push and watch the pipeline run. - Jenkins path: create a multibranch pipeline pointed at your repo. Configure the same two credentials in the Jenkins credentials store. Add the Jenkinsfile from the lesson. Trigger a build.
- Force a failure by breaking one assertion. Confirm the artifacts (screenshots, videos, HTML report) are archived on the failure step.
- JUnit integration — install
mocha-junit-reporter(chapter 7). Configure Cypress to emit JUnit XML alongside Mochawesome JSON. Confirm the Jenkins or GitLab test-result dashboard picks up the XML and shows per-test status. - Nightly job — schedule the same pipeline to run at 03:00 UTC. In GitLab, set up a pipeline schedule. In Jenkins, add
triggers { cron('H 3 * * *') }to the pipeline. - Stretch: if you have access to both platforms, set up the same project on both. Compare run times, artifact UX, and dashboard quality. Pick the one your team will actually look at — that's the right tool for you.
The next lesson goes from "one machine running tests serially" to "ten machines running tests in parallel" — the load-balancing strategies that turn 30-minute suites into 5-minute suites.