Intro
GitLab CI/CD automates software delivery through pipelines defined in a .gitlab-ci.yml file, but testing changes directly on a production instance risks disrupting real projects and exposing sensitive data. A local lab creates a safe, isolated environment where you can experiment with pipeline configurations, debug failures, and learn CI/CD concepts without consequences. This guide walks through setting up a local GitLab instance and GitLab Runner on a single Linux machine, with practical examples that demonstrate core CI/CD concepts. By the end, you will have a repeatable setup for validating pipelines before they reach shared infrastructure.
Version and Environment Inventory
Before starting, confirm your environment meets the requirements. The instructions assume a Linux host; Ubuntu 22.04 LTS is used in examples, but similar steps apply to other distributions with minor changes.
Software Versions
| Component | Version | Notes |
|---|---|---|
| GitLab CE | 16.10.0 | Installed via Omnibus package |
| GitLab Runner | 16.10.0 | Installed via official repository |
| Docker | 24.0.5 | Used as runner executor (optional) |
| OS | Ubuntu 22.04 LTS | Kernel 5.15 |
Topology
A single machine hosts both GitLab and the runner. GitLab runs as a system service on HTTPS port 443 (or HTTP port 80 if no TLS is configured). The runner registers with GitLab using a registration token and executes jobs locally using the shell executor by default. For containerized jobs, a Docker executor can be configured if Docker is installed.
Prerequisites
- 4 GB RAM minimum (8 GB recommended for smoother performance)
- 10 GB free disk space
- Sudo or root access on the machine
- Internet connectivity for downloading packages
Installation Steps
First, update package lists and install required dependencies:
sudo apt update && sudo apt install -y curl openssh-server ca-certificates tzdata perl
Add the GitLab package repository and install GitLab CE. Replace http://gitlab.local with your machine's IP or hostname. The installation may take several minutes.
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo EXTERNAL_URL="http://gitlab.local" apt install gitlab-ce
Install GitLab Runner using the official repository script:
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt install gitlab-runner
Verify GitLab is running by checking the status of its services:
sudo gitlab-ctl status
Expected output shows a list of services with run status, for example:
run: gitlab-workhorse: (pid 1234) 10s; run: log: (pid 1233) 10s
run: logrotate: (pid 1235) 10s; run: log: (pid 1232) 10s
...
After installation, access the GitLab web interface by navigating to the configured URL. During the first visit, you will be prompted to set a password for the root user.
Safe Configuration Path
Start with a minimal configuration that limits exposure and simplifies debugging. This section covers creating a test project, registering a runner, and defining a basic pipeline.
Create a Project
Create a blank project in GitLab via the web UI or API. For automation, use the API with a personal access token (generate one under User Settings > Access Tokens with api scope).
curl --request POST --header "PRIVATE-TOKEN: <your_token>" \
--data "name=my-test-project&visibility=private" \
"http://gitlab.local/api/v4/projects"
Expected output includes the new project's details, such as "id":1.
Register a Runner
Register a runner with the shell executor to avoid extra dependencies. First, obtain the project's registration token from the web UI under Settings > CI/CD > Runners (or use the instance-level token for shared runners). Then run:
sudo gitlab-runner register
When prompted, enter the following:
- GitLab instance URL (e.g.,
http://gitlab.local) - Registration token from the project's CI/CD settings
- Description (e.g.,
local-shell-runner) - Tags (optional, e.g.,
shell) - Executor:
shell
After registration, the runner configuration file /etc/gitlab-runner/config.toml will contain an entry similar to:
[[runners]]
name = "local-shell-runner"
url = "http://gitlab.local"
token = "xxxxxxxxxxxx"
executor = "shell"
Define a Basic Pipeline
Create a .gitlab-ci.yml file in the project repository. This simple pipeline defines two stages (build and test) and prints environment information. The build-job creates an artifact that the test-job verifies.
stages:
- build
- test
before_script:
- echo "Starting job $CI_JOB_NAME"
build-job:
stage: build
script:
- echo "Building the project..."
- mkdir -p output
- echo "Build artifact" > output/build.txt
artifacts:
paths:
- output/
test-job:
stage: test
script:
- echo "Running tests..."
- test -f output/build.txt && echo "Artifact found"
Commit and push this file to trigger the pipeline. The before_script runs before each job, printing the job name. The artifact from build-job is available in test-job because artifacts are passed between stages by default.
Scoped Choices
- Use the shell executor initially to avoid Docker configuration complexities.
- Keep the project private to restrict access.
- Use a separate runner for each project to isolate resources (or use tags to direct jobs).
Verification and Diagnostics
After pushing the pipeline configuration, verify that jobs run successfully and inspect artifacts.
Pipeline Status
Check pipeline status via the web UI (CI/CD > Pipelines) or using the GitLab CLI (glab) if installed:
glab ci status
If using the API, retrieve the latest pipeline for project ID 1:
curl --header "PRIVATE-TOKEN: <your_token>" \
"http://gitlab.local/api/v4/projects/1/pipelines/latest"
Look for "status":"success" in the JSON response.
Job Logs
View job logs to confirm each step executed correctly. In the web UI, navigate to CI/CD > Pipelines, then click the pipeline to see its jobs. Click on a job to see its log. For a shell runner, logs are also stored on the runner machine in /var/log/gitlab-runner/, but the UI provides the easiest access.
Example job log excerpt for build-job:
$ echo "Starting job build-job"
Starting job build-job
$ echo "Building the project..."
Building the project...
$ mkdir -p output
$ echo "Build artifact" > output/build.txt
Uploading artifacts for successful job
Runner Verification
Ensure the runner is active and connected:
sudo gitlab-runner verify
Output:
Verifying runner... is alive
Common Diagnostic Commands
sudo gitlab-ctl tail- View GitLab logssudo gitlab-runner --debug run- Run runner in debug mode (foreground)curl -I http://gitlab.local- Check web server response headers
Failure Modes and Recovery
Common failures include runner connectivity issues, permission errors, and misconfigured pipelines. This section describes how to diagnose and recover.
Runner Not Picking Up Jobs
Symptoms: Pipeline shows pending and the runner does not execute jobs.
Diagnosis: Check runner status:
sudo gitlab-runner status
Check runner registration:
sudo gitlab-runner list
Recovery: Ensure the runner is registered for the correct project and that the token matches. If not, re-register with the correct token:
sudo gitlab-runner register
Also verify network connectivity between runner and GitLab (ping, curl).
Permission Denied Errors
Symptoms: Job fails with Permission denied when writing files.
Diagnosis: The shell executor runs as the gitlab-runner user, which may not have write permissions in the working directory (default: /home/gitlab-runner/builds).
Recovery: Adjust permissions or use a different executor. For a temporary fix, add the gitlab-runner user to the appropriate group (e.g., www-data if building web files) and restart the runner:
sudo usermod -aG www-data gitlab-runner
sudo systemctl restart gitlab-runner
For persistent issues, consider using the Docker executor to isolate file permissions.
Invalid YAML Configuration
Symptoms: Pipeline fails immediately with a YAML syntax error.
Diagnosis: Use a YAML linter or the GitLab CI Lint tool available in the web UI (CI/CD > Pipelines > CI Lint). Paste the .gitlab-ci.yml content to see validation errors.
Recovery: Fix the YAML and push again. Common issues include incorrect indentation, missing colons, or using tabs instead of spaces.
Docker Executor Issues (if used)
Symptoms: Job fails with Cannot connect to the Docker daemon.
Diagnosis: The Docker service may not be running, or the runner lacks permissions to access the Docker socket.
Recovery: Start Docker and add the runner user to the docker group:
sudo systemctl start docker
sudo usermod -aG docker gitlab-runner
sudo systemctl restart gitlab-runner
Verify that the runner can run Docker containers:
sudo -u gitlab-runner docker run hello-world
Rollback
To revert changes, simply edit .gitlab-ci.yml to the last known good version and push. For runner configuration, restore /etc/gitlab-runner/config.toml from a backup or re-register the runner.
Operations Checklist
Use this checklist when setting up or modifying your local GitLab CI/CD lab.
| Task | Command / Action | Expected Result |
|---|---|---|
| Verify GitLab is running | sudo gitlab-ctl status | All services show run |
| Check runner status | sudo gitlab-runner status | gitlab-runner: Service is running |
| List registered runners | sudo gitlab-runner list | Shows runner with correct URL and executor |
| Validate YAML syntax | Use CI Lint in web UI | No syntax errors |
| Push pipeline | git push origin main | Pipeline triggered and succeeds |
| Check job logs | Via web UI or API | No unexpected errors |
| Verify artifacts | Download from pipeline page | Files present |
| Cleanup old projects | Delete project or pipeline | Resources freed |
Regularly update GitLab and the runner to the latest patch versions to maintain compatibility and security. For example, run sudo apt update && sudo apt upgrade gitlab-ce gitlab-runner monthly.
Conclusion
A local GitLab CI/CD lab is a valuable asset for developing and testing pipelines safely. By following this guide, you have installed GitLab and a runner, created a test project, defined a basic pipeline, and learned how to verify and troubleshoot common issues. Next steps include experimenting with more complex pipeline features such as environments, multi-stage deployments, and containerized jobs. With this foundation, you can confidently validate CI/CD configurations before promoting them to production.