GitLab CI/CD and Jenkins: pipelines, runners and quality gates
How I build and run delivery pipelines, shown through a GitLab pipeline for a TypeScript monorepo, a self-hosted runner, and the Jenkins and quality-gate work around them.
- Install dependencies npm ci, cache keyed per branch
- Build shared packages, then the service Nx production build, Nx daemon off
- Check the build output exists fail early, before docker build
- Wait for the Docker daemon, build the image tags: commit SHA · branch slug · latest
- Push SHA and branch tags always · latest only from the default branch
Scope
Across client and internal projects, I:
- built and maintained CI/CD pipelines with GitLab CI/CD and Jenkins across development, staging and production;
- set up self-hosted GitLab Runners and Jenkins build agents, maintained Jenkins controllers (upgrades, plugins, backups), wrote Jenkins pipelines in Groovy and integrated GitLab with Jenkins;
- installed and configured SonarQube and Nexus and wired them into pipelines, and deployed Harbor with image scanning and controlled image promotion;
- added blocking quality and security gates (SonarQube quality gate, Trivy severity threshold) and smoke and health-check stages after deployment;
- set up GitLab protected environments with manual approvals between environments, reusable pipeline templates and shared Helm charts for development teams.
The pipeline I can show
A TypeScript monorepo built with Nx: three services and shared packages, one GitLab job per service, each producing a Docker image pushed to a Nexus registry. The diagram above follows the actual definition, with names removed.
Problems the definition handles
- Jobs that never finished. The Nx daemon kept running after the build and held the job open. The pipeline turns it off (
NX_DAEMON=false). - Native modules on Alpine. A dependency with a native addon failed to install on the slim Node image, so the job installs
python3,makeandg++first. Trade-off: every job pays for that install; a prebuilt build image would avoid it. - Docker-in-Docker not ready yet. A job could reach
docker buildbefore the DinD service accepted connections. A readiness loop (30 tries, 2 seconds apart) runsdocker infofirst. - Fail early, with a clear message. Before building the image, the job checks that the expected build output exists, so a broken build fails in the pipeline log rather than deep inside the Dockerfile.
- Traceable tags. Each image gets the commit SHA and the branch slug.
latestmoves only from the default branch, so a feature branch can never change whatlatestmeans. - Selective rebuilds. Merge requests, tags and the default branch always build. On other branches a service job runs only when that service, the shared packages, the workspace config or the pipeline file change; otherwise it waits for a manual start and may fail without blocking the pipeline.
The runner
A self-hosted GitLab Runner with the Docker executor, configured for Docker-in-Docker builds:
- builds and
/tmpon tmpfs, so dependency installs and image layers hit RAM instead of disk; - per-job limits of 4 CPUs, 8 GB of memory and 2 GB of shared memory, with six concurrent jobs;
- a job network MTU of 1300 and fixed DNS servers for jobs and service containers. A lower MTU is the usual fix when the host’s network path carries less than Docker’s default 1500 bytes.
Trade-off: Docker-in-Docker needs privileged mode, so every job on this runner must be trusted code. Rootless builders such as Kaniko or BuildKit avoid that, at the cost of changing how images are built.
Policy as a pipeline check
My N27 project shows the same idea in a public-safe form. Its CI workflow fails if any Compose service mounts the Docker socket or runs privileged, validates the default stack and every optional profile with fake CI-only inputs, and runs API integration tests against a real PostgreSQL with the restricted application role.
Jenkins
The Jenkins work ran on client infrastructure, so there is nothing I can publish: no Jenkinsfiles and no screenshots. The scope was build agent installation and configuration, controller maintenance (upgrades, plugins, backups), Groovy pipelines, GitLab–Jenkins integration, LDAP / Active Directory sign-in for CI/CD tools, and publishing test reports from automated suites triggered by the pipelines.