Intro
GitLab CI/CD pipelines automate the path from code commit to deployment, but when a pipeline fails, the error messages can be cryptic and the root cause elusive. Whether you are a developer pushing code, a DevOps consultant managing multiple projects, or a technical startup team building your first CI/CD workflow, systematic troubleshooting skills are essential. This guide provides practical, hands-on methods to diagnose and resolve common GitLab CI/CD failures. We will cover environment inventory, safe configuration changes, verification commands, failure modes and recovery, an operations checklist, and common pitfalls. By the end, you will be able to approach pipeline issues with confidence, minimize downtime, and implement robust fixes.
Version and Environment Inventory
Before making any changes, establish a clear baseline of your GitLab environment. This prevents misdiagnosis and ensures you are working with the correct versions and components. Document all findings in a shared location such as the project wiki or a dedicated operations document.
1.1 Gather GitLab Instance Information
Start by identifying your GitLab instance version and edition. This information is available in the admin area or via the API. For self-managed instances, the admin area is at Admin Area > Overview > Dashboard. On GitLab.com, use the API endpoint.
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/version"
Expected output:
{ "version": "16.7.0-ee", "revision": "abc1234" }
Note whether you are using GitLab.com or a self-managed instance. Self-managed instances allow more control but require you to manage upgrades and runner infrastructure. Also record the instance URL, the authentication method (SSO, LDAP, built-in), and any proxy settings. For example, if your company uses an internal proxy, note its address and port because it will affect network troubleshooting.
1.2 Identify GitLab Runner Versions
Runners execute jobs. List all runners and their statuses by running the following command on each runner host:
gitlab-runner list
Example output:
Runtime platform arch=amd64 os=linux pid=1234 revision=abcdef12 version=16.7.0 Listing configured runners ConfigFile=/etc/gitlab-runner/config.toml my-runner Executor=docker Token=xxxxxx URL=https://gitlab.example.com
For each runner, check its executor type (shell, docker, kubernetes, etc.) because troubleshooting steps differ. Use gitlab-runner verify to check registration:
gitlab-runner verify
Expected output:
Runtime platform arch=amd64 os=linux pid=1234 revision=abcdef12 version=16.7.0 Running in system-mode.
Verifying runner... is alive runner=xxxxxx
Note the runner's tags, which determine which jobs it can pick up. For example, a runner with tag docker will only run jobs that specify that tag. Also record the runner's concurrency setting and available resources (CPU, memory, disk) because these affect job performance.
To see all runners registered to your GitLab instance, use the admin API:
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all"
This returns a JSON array with each runner's id, description, status, tags, and more. Save this output for reference.
1.3 Document Pipeline Topology
Map your pipeline stages and jobs. List the stages defined in .gitlab-ci.yml and identify external dependencies such as artifact repositories, deployment targets, or external services. Create a simple diagram using a tool like Mermaid or draw.io. For each stage, note the following:
- The runner tags required.
- The environment variables or secrets used.
- The external services accessed (e.g., npm registry, Docker registry, AWS S3).
- The expected duration and criticality.
Example topology for a web application:
| Stage | Jobs | Runner Tags | External Dependencies |
|---|---|---|---|
| build | install deps, compile | docker | npm registry, artifact storage |
| test | unit tests, lint | docker | test database |
| deploy-stg | deploy to staging | shell | staging server, SSH keys |
| deploy-prod | deploy to production | shell | production server, approval rules |
This inventory helps when tracing failures across stages. For example, if the deploy-stg job fails, you know to check the SSH key and staging server availability.
1.4 Review Prerequisites
Ensure the following prerequisites are met:
- GitLab instance reachable from runner machines. Test with
pingandcurlto the GitLab URL. - Runner has necessary permissions (e.g., to pull images, access repositories). Check the runner's registration token and any project-specific settings.
- Network connectivity to external services (if used). Test with
nc -zv registry.example.com 443. - Required secrets or variables configured in GitLab CI/CD settings. List all variables in Project Settings > CI/CD > Variables and verify they are set correctly and not expired.
Document any missing prerequisites and assign an owner to resolve them before proceeding.
Safe Configuration Path
When modifying pipeline configuration, adopt a scoped, incremental approach to minimize risk and ease rollback. All changes should go through the version control system, and you should have a clear rollback plan before making any change.
2.1 Use Feature Flags and Conditional Logic
Introduce changes behind environment variables or rules. For example, to test a new deployment method on a specific branch, use rules in .gitlab-ci.yml:
deploy: script: ./deploy.sh rules:
- if: '$CI_COMMIT_BRANCH == "main" && $ENABLE_NEW_DEPLOY == "true"'
Set ENABLE_NEW_DEPLOY as a variable in the project settings, initially "false". This allows quick rollback by toggling the variable to "false" or removing it. The owner of this decision is the DevOps lead (e.g., Priya Shah, Engineering Lead), who reviews the variable weekly to decide whether to keep the feature or remove it.
2.2 Keep Configurations in Version Control
All pipeline configuration should be in .gitlab-ci.yml stored in the repository. Use merge requests for changes and require review. This provides a natural audit trail and rollback via revert. For example, if a change breaks the pipeline, you can revert the merge request with a single click in the GitLab UI or by running git revert <commit-sha>.
2.3 Use Includes for Modular Changes
Break large configurations into reusable snippets using include:
include:
- local: '/ci/templates/deploy.yml'
When changing a specific part, only modify that file, reducing impact. Keep the include files in the same repository under a ci/ directory. This also makes it easier to review changes because the diff is limited to one file.
2.4 Test Changes in a Safe Environment
For self-managed instances, consider using a staging GitLab instance or a separate project to validate changes before applying to production. For GitLab.com, use a separate branch and limited trigger rules. For example, create a test project that mirrors the production project's CI configuration and use it to test new runner configurations or pipeline changes.
2.5 Example: Scoped Change to a Job
Suppose you need to update a Docker image version in a job. Instead of directly editing the job in the main branch, create a feature branch:
build: image: node:16 script: npm run build
Change to:
build: image: node:18 script: npm run build
Push to a branch and observe the pipeline. If it fails, revert the commit and push again. This limits the impact to the feature branch and allows you to test before merging to main. The owner for this change is the developer making it, and the expected review turnaround is within one business day.
Verification and Diagnostics
After making changes, verify that the pipeline behaves as expected. Use logs and diagnostic commands to confirm. Always compare the new behavior with the documented baseline from Section 1.
3.1 Check Pipeline Status and Logs
Use the GitLab UI to view pipeline logs. For command-line access, use the API:
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/pipelines/:pipeline_id/jobs"
Example output includes job statuses and IDs. To fetch a specific job log:
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/jobs/:job_id/trace"
The trace is plain text, which you can grep for errors. For example, to find all error lines in a job log, save the trace to a file and run:
grep -i "error" job_trace.log
This helps quickly identify the failing command.
3.2 Verify Runner Connectivity
On the runner machine, test connectivity to GitLab:
gitlab-runner --debug run --config /etc/gitlab-runner/config.toml
Look for "Checking for jobs..." and "Received job" messages. If the runner cannot connect, check network firewall and token. Also run a simple curl from the runner host to the GitLab URL:
curl -I https://gitlab.example.com
Expected output: HTTP/1.1 302 Found or 200 OK. If you get a connection timeout, check DNS and firewall rules.
3.3 Diagnose Docker-related Failures
For Docker executors, ensure the runner can pull images. Run a simple job with docker pull:
docker pull node:18
Expected output:
18: Pulling from library/node Digest: sha256:... Status: Image is up to date for node:18
If pull fails, check registry authentication and runner configuration. For private registries, ensure the runner has the correct credentials in its config.toml or environment variables. Test authentication manually:
docker login registry.example.com
Then pull again. If the image is large, consider using a local mirror or cache.
3.4 Validate Pipeline Syntax
Before committing, validate .gitlab-ci.yml syntax using the GitLab CI Lint tool available in the UI under CI/CD > Pipelines > CI Lint, or via API:
curl --header "Content-Type: application/json" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/ci/lint" --data '{"content": "job:\n script: echo hello"}'
Expected output includes "status": "valid" or errors. Always lint the full file content, not just a snippet. You can also use the gitlab-ci-lint command line tool if available.
3.5 Inspect Runner Logs
Runner logs often contain detailed error messages. Location depends on installation:
- Systemd:
journalctl -u gitlab-runner - Linux package:
/var/log/gitlab-runner/ - Docker container:
docker logs <container>
Example:
journalctl -u gitlab-runner -n 50
Look for patterns like "ERROR: Job failed: execution took longer than", "ERROR: Preparation failed", etc. These patterns indicate common issues. For example, "execution took longer than" suggests a job timeout, which you can increase in the job definition with timeout: 2h.
Failure Modes and Recovery
Understanding common failure modes and how to recover from them is crucial for maintaining uptime. The following table summarizes the most frequent failure modes, symptoms, and causes.
4.1 Common Failure Modes
| Failure Mode | Typical Symptoms | Common Causes |
|---|---|---|
| Runner unavailable | Jobs stuck in pending; "This job is stuck because the project doesn't have any runners online" | Runner offline, misconfigured tags, insufficient resources |
| Authentication failure | "Authentication failed" when pulling images or accessing repositories | Expired tokens, incorrect credentials, missing deploy keys |
| Network timeouts | "fatal: unable to access '...': Failed to connect" | Firewall blocking, DNS issues, proxy misconfiguration |
| Resource exhaustion | Job killed, out-of-memory errors | Insufficient runner memory or disk, too many concurrent jobs |
| Script errors | Non-zero exit code from script | Missing dependencies, incorrect paths, environment variables not set |
| Artifact upload failure | "Uploading artifacts... failed" | Insufficient permissions, storage quota exceeded, network interruption |
4.2 Rollback and Recovery Steps
Each failure mode has a specific recovery procedure. Always document the exact commands run and the results in the incident log.
Scenario 1: Runner unavailable
- Check runner status:
gitlab-runner status - If stopped, start it:
sudo gitlab-runner start - If runner is registered but not picking jobs, verify tags match job requirements. Check the job's tags in
.gitlab-ci.ymland the runner's tags inconfig.toml. - If misconfigured, edit
config.tomland restart:sudo gitlab-runner restart.
Example: A job with tag docker is pending because the only online runner has tag shell. Fix by changing the job tag or adding a runner with the correct tag.
Scenario 2: Authentication failure
- For Docker registry, re-authenticate:
docker login registry.example.com - For Git repository access, re-add SSH keys or deploy tokens.
- Update CI/CD variables in project settings. Check for expired tokens and rotate them.
Example: A job fails to pull a private image with "Authentication failed". The fix is to update the DOCKER_AUTH_CONFIG variable with new credentials and re-run.
Scenario 3: Resource exhaustion
- Increase runner resources or reduce concurrent jobs (
concurrentinconfig.toml). - Add cleanup steps to jobs to free disk space, e.g.,
rm -rf ~/.cachein the script. - Use
gitlab-runner cleanupto remove old build directories. - Monitor disk usage with
df -hand memory withfree -m.
Example: A job fails with "out of memory" when running tests. Increase the runner's memory or split the test suite into parallel jobs.
Scenario 4: Script errors
- Re-run job with debug logging: set
CI_DEBUG_TRACE=truein project variables to get verbose output. - Check for missing dependencies and add installation steps. For example, if
pythonis missing, addapt-get install python3or use a Docker image that includes it. - Review the script for hardcoded paths and incorrect assumptions about the runner environment.
Scenario 5: Artifact upload failure
- Ensure
artifacts: pathsare correctly specified. Use absolute paths or paths relative to the build directory. - Increase artifact size limit if needed (admin setting). Default is 100 MB.
- Check storage permissions. For local storage, ensure the GitLab user has write access to the artifacts directory.
4.3 Recovery Verification
After applying fixes, re-run the pipeline and verify all jobs pass. Check logs for expected outputs. Use the checklist below to confirm recovery.
- [ ] Pipeline status is green.
- [ ] All jobs completed within expected time.
- [ ] No error patterns in job logs.
- [ ] Artifacts uploaded correctly and can be downloaded.
- [ ] Deployment (if any) successful and application healthy.
The recovery owner (e.g., DevOps engineer) should document the incident and update the operations runbook with any new findings.
Operations Checklist
Use this repeatable checklist for ongoing operations and troubleshooting. Assign a single owner for each checklist item and a review frequency. The default owner is the DevOps lead unless otherwise specified.
Before Changes
- [ ] Document current GitLab and runner versions. Owner: DevOps lead. Frequency: monthly.
- [ ] Note pipeline topology and dependencies. Owner: Project lead. Frequency: quarterly or on major change.
- [ ] Ensure configuration is in version control. Owner: All developers. Frequency: every commit.
- [ ] Identify rollback points (e.g., previous commit, variable toggle). Owner: Developer making change. Frequency: per change.
During Changes
- [ ] Make scoped changes (one change per merge request). Owner: Developer. Frequency: per change.
- [ ] Use rules to limit impact (e.g., only on specific branches). Owner: DevOps lead. Frequency: per change.
- [ ] Validate
.gitlab-ci.ymlsyntax. Owner: Developer. Frequency: every commit to CI file.
After Changes
- [ ] Monitor pipeline status. Owner: Developer. Frequency: immediate after change.
- [ ] Check runner logs for errors. Owner: DevOps engineer. Frequency: after each pipeline run or daily.
- [ ] Verify expected outputs in job logs. Owner: Developer. Frequency: after each pipeline run.
- [ ] Test rollback if necessary. Owner: DevOps lead. Frequency: as needed.
Periodic Review
- [ ] Update runners and GitLab instance regularly. Owner: DevOps lead. Frequency: monthly or as per maintenance window.
- [ ] Review project CI/CD settings and variables. Owner: DevOps lead. Frequency: monthly.
- [ ] Clean up old artifacts and caches. Owner: DevOps engineer. Frequency: weekly.
- [ ] Review pipeline performance and optimize. Owner: DevOps lead. Frequency: quarterly.
Common Pitfalls and How to Avoid Them
Even experienced teams fall into common traps when troubleshooting GitLab CI/CD. Here are the most frequent pitfalls, why they happen, and how to avoid or recover from them.
Pitfall 1: Ignoring Runner Tags
Why it happens: Developers assume any runner can pick up a job, but jobs with specific tags only run on runners with matching tags. If a job is stuck in pending, the first check should be runner tags.
How to avoid: Document runner tags in the project README and in the pipeline topology. In .gitlab-ci.yml, use meaningful tags and minimize the number of tags per runner.
How to recover: Run gitlab-runner list to see runner tags and compare with the job's tags. Either change the job tag or add a runner with the correct tag.
Pitfall 2: Hardcoding Secrets in .gitlab-ci.yml
Why it happens: It is convenient to put passwords or tokens directly in the configuration, but this exposes secrets in the repository and makes rotation difficult.
How to avoid: Use CI/CD variables with masking and protection. Store secrets in Project Settings > CI/CD > Variables and reference them as $VARIABLE_NAME. For sensitive files, use file-type variables.
How to recover: If a secret was leaked, immediately revoke it and rotate. Then remove the hardcoded value and replace with a variable. Scan the repository history for leaks and force-push a cleaned history if necessary.
Pitfall 3: Not Using Caching Effectively
Why it happens: Teams often overlook caching, leading to slow pipelines and unnecessary load on external dependencies. Without caching, every job re-downloads dependencies.
How to avoid: Use cache and artifacts appropriately. Cache dependencies like npm modules or Maven repositories. Example:
cache: paths:
- node_modules/
How to recover: If pipeline is slow, check job logs for repeated dependency downloads. Add cache sections to the relevant jobs and re-run.
Pitfall 4: Overlooking Runner Resource Limits
Why it happens: Runners may have insufficient memory or disk for build tasks, causing flaky failures. This is common when parallel jobs increase resource usage.
How to avoid: Monitor runner host metrics with tools like Prometheus or htop. Set appropriate concurrent and limit in config.toml. Use Docker resource limits if using Docker executor.
How to recover: Reduce concurrency, add more runners, or scale up the runner host. For Docker executor, set memory and cpus in the runner config.
Pitfall 5: Neglecting Runner Updates
Why it happens: Teams often run outdated runners, which can lead to compatibility issues with newer GitLab features and security vulnerabilities.
How to avoid: Subscribe to GitLab release notes and schedule monthly runner updates. Use the same version for GitLab and runners when possible.
How to recover: Update the runner using the package manager: sudo apt-get update && sudo apt-get install gitlab-runner (or equivalent). Verify with gitlab-runner --version.
Pitfall 6: Failing to Plan Rollbacks
Why it happens: Changes are made hastily without considering how to revert if something goes wrong, leading to extended downtime.
How to avoid: Always have a rollback plan before making changes. Use feature flags, version control, and incremental changes as described in Section 2. Keep previous working versions of config.toml and .gitlab-ci.yml.
How to recover: If a change breaks the pipeline, immediately revert the commit or toggle the feature flag. Document the rollback procedure in the runbook.
Conclusion
Troubleshooting GitLab CI/CD pipelines involves a systematic approach: gather environment information, make safe configuration changes, verify with observable checks, and know how to recover from failures. By following the practical commands and checklists in this guide, you can reduce downtime and maintain robust CI/CD processes. Start by applying these techniques to your next pipeline failure, and gradually build a library of known issues and solutions for your team. Remember to keep configurations version-controlled and changes incremental for easier rollback. With practice, you will turn troubleshooting from a reactive scramble into a proactive, manageable routine. Assign clear owners for each checklist item and review the operations checklist regularly to ensure continuous improvement.