A local GitLab Runner lab gives you a safe, resettable place to test jobs, troubleshoot behavior, and learn by doing without touching shared infrastructure. In this guide you will:
- Install GitLab Runner for local use.
- Choose a safe configuration that limits blast radius.
- Run two practical examples using the shell and Docker executors.
- Verify results and capture diagnostics.
- Recognize common failure modes and recover fast.
- Use a repeatable checklist for day-to-day operations.
The core idea is simple: keep your first pilot narrow, measurable, and easy to inspect locally before any broader rollout. That approach speeds up learning and cuts rework.
Version and Environment Inventory
Before touching anything, write down exactly what you will run. It turns "works on my machine" into repeatable steps your teammates can execute.
| Item | Example value | Why it matters |
|---|---|---|
| OS | Ubuntu 22.04 LTS x86_64 | Determines packages, paths, and service tools |
| Hostname | dev-ws-01 | Distinguishes logs and lab artifacts |
| Lab user | runnerlab (non-root) | Limits privileges and scope |
| GitLab Runner | 16.x (amd64 binary) | Reproducible flags and behavior |
| Optional runtime | Docker 24.x, group=docker | Required for Docker executor experiments |
| CPU/RAM budget | 2 vCPU, 4 GB RAM | Sets expectations for job duration |
Tips:
- Use a dedicated non-root account for all lab actions.
- Keep lab files under a single directory tree so you can back it up or delete it in one step.
- If you will experiment with Docker, ensure your user is in the docker group and that images are pre-pulled when the network is slow or restricted.
Safe Configuration Path
A safe local lab is intentionally small and controlled. The following choices keep you fast and safe:
- Scope: Start with local "exec" runs (no registration to a GitLab server). You can layer on registration later if you want to test full pipelines, but local exec is enough for most experiments.
- Identity: Create a dedicated non-root user, for example "runnerlab".
- Directories: Use per-lab directories like
~/lab/work,~/lab/cache, and~/lab/logs. Do not use system directories. - Executor choices:
- Shell executor: simplest, uses your host tools. Great for command prototyping and quick checks.
- Docker executor: optional, helpful when you need a clean toolchain or OS-level isolation.
- Privileges: Avoid privileged containers and avoid mounting sensitive host paths.
- Resource guardrails: Keep jobs small. If using Docker, avoid huge images and pre-pull what you need. On the host, avoid running memory-starving jobs.
A quick comparison to guide your choice:
| Executor | When to use | Pros | Cons |
|---|---|---|---|
| shell | Fast local commands and scripts | Simple, no images needed | Shares host toolchain and environment |
| docker | Toolchain isolation and reproducibility | Clean environment, easy language setup | Needs Docker and images, possible permissions issues |
Implementation Steps
The commands below focus on Linux (Ubuntu/Debian-like). Adjust paths for other platforms.
1. Create a dedicated lab user
# Create a non-root user for lab work
sudo useradd -m -s /bin/bash runnerlab
# Create lab directories (as the lab user)
sudo -iu runnerlab bash -lc '
mkdir -p ~/lab/{work,cache,logs}
printf "Lab directories created under: %s\n" "$HOME/lab"
'
2. Install GitLab Runner (standalone binary)
# Download and install the GitLab Runner binary
sudo curl -L -o /usr/local/bin/gitlab-runner \
https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
sudo chmod +x /usr/local/bin/gitlab-runner
# Verify installation
gitlab-runner --version
# Expected result: a version line like "Version: 16.x" plus build info.
3. (Optional) Install Docker for the Docker executor
# Install Docker engine (Ubuntu example)
sudo apt-get update
sudo apt-get install -y docker.io
# Allow the lab user to access the Docker socket
sudo groupadd -f docker
sudo usermod -aG docker runnerlab
# Refresh group membership in the current shell (new login also works)
sudo -iu runnerlab bash -lc 'id && docker info >/dev/null && echo Docker OK'
# Expected result: the lab user is in the docker group and "docker info" succeeds without sudo.
Practical Examples
The examples use gitlab-runner exec which runs a job from a local .gitlab-ci.yml without registering a runner with a GitLab instance.
Important notes:
- Each example runs in your current working directory, so keep files under
~/lab/work. - You can override build and cache locations with flags to keep everything inside your lab directory.
Example 1: Shell executor, print system info
1. Prepare a working directory and minimal pipeline file
sudo -iu runnerlab bash -lc '
mkdir -p ~/lab/work/example1 && cd ~/lab/work/example1
cat > .gitlab-ci.yml <<"YAML"
lab_echo:
stage: test
script:
- echo "Hello from shell executor"
- uname -a
- printf "PWD=%s\n" "$PWD"
- echo "Done"
YAML
printf "Created: %s/.gitlab-ci.yml\n" "$PWD"
'
2. Run the job locally with the shell executor
sudo -iu runnerlab bash -lc '
cd ~/lab/work/example1
gitlab-runner exec shell lab_echo \
--builds-dir "$HOME/lab/work" \
--cache-dir "$HOME/lab/cache" \
2>&1 | tee "$HOME/lab/logs/example1_shell.log"
'
Expected result:
- Output begins with "Running with gitlab-runner..." and "Using Shell (bash) executor".
- Your echo, uname, and PWD lines appear.
- Final line shows "Job succeeded".
To re-run cleanly, just re-execute the same command; nothing is left behind except the log you chose to save.
Example 2: Docker executor, run Python test
This example runs a tiny Python test in a container image to demonstrate isolation.
1. Prepare files
sudo -iu runnerlab bash -lc '
mkdir -p ~/lab/work/example2/tests && cd ~/lab/work/example2
# A tiny test file
cat > tests/test_math.py <<"PY"
import math
def test_add():
assert 2 + 2 == 4
def test_sqrt():
assert math.isclose(math.sqrt(9), 3.0)
PY
# Minimal pipeline that installs pytest and runs tests
cat > .gitlab-ci.yml <<"YAML"
lab_pytest:
image: python:3.11-slim
stage: test
before_script:
- python --version
- pip install --no-cache-dir pytest==7.4.4
script:
- pytest -q
YAML
printf "Created example2 with tests and .gitlab-ci.yml\n"
'
2. Pre-pull the image (optional but speeds up first run)
sudo -iu runnerlab docker pull python:3.11-slim
3. Run the job locally with the Docker executor
sudo -iu runnerlab bash -lc '
cd ~/lab/work/example2
gitlab-runner exec docker lab_pytest \
--builds-dir "$HOME/lab/work" \
--cache-dir "$HOME/lab/cache" \
--docker-pull-policy if-not-present \
2>&1 | tee "$HOME/lab/logs/example2_docker.log"
'
Expected result:
- Output shows "Using Docker executor" and the python:3.11-slim image.
- Python version prints, pytest installs, and 2 tests pass.
- Final line shows "Job succeeded".
Verification and Diagnostics
These checks confirm your lab is healthy and make troubleshooting faster.
| Check | Command | Expected result |
|---|---|---|
| Runner version | gitlab-runner --version | Version line with 16.x info |
| Shell example log exists | ls -l ~runnerlab/lab/logs/example1_shell.log | File present with recent timestamp |
| Docker example log exists | ls -l ~runnerlab/lab/logs/example2_docker.log | File present with recent timestamp |
| Docker group membership | id runnerlab | Shows "docker" in groups |
| Docker connectivity | sudo -iu runnerlab docker info | Succeeds without sudo |
Additional tips:
- Increase verbosity: add
--debugto any gitlab-runner command.
sudo -iu runnerlab bash -lc '
cd ~/lab/work/example1
gitlab-runner --debug exec shell lab_echo | sed -n "1,60p"
'
- Force a clean workspace by removing build directories under your lab work path if a prior run left artifacts.
- For Docker executor, pre-pull images and verify image digests when you need reproducibility.
- Pin language tools or test framework versions in your job scripts so re-runs behave the same.
Failure Modes and Recovery
Common issues and quick fixes:
1. Permission denied on Docker socket
- Symptom: "Got permission denied while trying to connect to the Docker daemon socket"
- Fix:
sudo usermod -aG docker runnerlab
# New login required to pick up group; then verify
sudo -iu runnerlab docker info
2. Command not found in shell job
- Symptom: A tool works in your login shell but not in the job.
- Cause: PATH differs or the tool is not installed for the lab user.
- Fix: Install the tool for the host or preface commands with a deterministic path, e.g.,
/usr/bin/env python3. In.gitlab-ci.ymlyou can also set PATH or install prerequisites inbefore_script.
3. YAML or quoting errors
- Symptom: gitlab-runner fails to parse the job or the shell treats arguments incorrectly.
- Fix: Use proper YAML indentation and quote strings with spaces. Example:
script:
- bash -lc 'echo "Value: $MYVAR"'
4. Docker image pulls are slow or fail
- Symptom: Timeouts or 429 errors when pulling.
- Fix: Pre-pull the image; use
--docker-pull-policy if-not-present; choose a smaller base image.
5. Residual files break re-runs
- Symptom: A previous run left build products that change behavior.
- Fix:
sudo -iu runnerlab bash -lc '
cd ~/lab/work/example2
git clean -fdx || true
'
6. Mismatched interpreter
- Symptom: "python: command not found" or shell differences.
- Fix: Use explicit interpreters (
python3). For non-bash defaults, run script lines throughbash -lc.
Rollback and recovery steps
- Revert to a known-good GitLab Runner version:
# Keep a copy of a working binary
sudo cp /usr/local/bin/gitlab-runner /usr/local/bin/gitlab-runner.good
# If you upgrade and regress, roll back quickly
sudo mv /usr/local/bin/gitlab-runner.good /usr/local/bin/gitlab-runner
sudo chmod +x /usr/local/bin/gitlab-runner
gitlab-runner --version
- Remove lab artifacts (soft reset):
sudo -iu runnerlab bash -lc 'rm -rf ~/lab/work/* ~/lab/cache/* ~/lab/logs/*'
- Uninstall completely (hard reset):
# Stop any user services you may have created (if any)
# Then remove user and binary
sudo userdel -r runnerlab || true
sudo rm -f /usr/local/bin/gitlab-runner
Verification after recovery:
- Confirm the runner version is what you expect.
- Confirm the lab directories are recreated before re-running examples.
- Re-run Example 1 to validate the baseline.
Operations Checklist
Use this checklist to keep experiments predictable and reviewable.
Preparation
- Confirm OS, user, and versions match your inventory table.
- Ensure the lab user exists and can run
gitlab-runner --version. - For Docker tests, confirm
docker infoworks for the lab user and required images are pulled.
Pre-run checks
- Ensure
.gitlab-ci.ymlexists in the working directory and job names match. - Optionally clear the working directory or run in a new folder.
- Decide on
builds-dirandcache-dirflags so artifacts stay in the lab tree.
Run
- Execute
gitlab-runner exec shell JOBorgitlab-runner exec docker JOB. - Capture stdout/stderr to a log under
~/lab/logs.
After run
- Check the final "Job succeeded" line.
- Review the log for warnings and durations.
- Save or delete artifacts under your lab work path as needed.
Periodic hygiene
- Prune old images if you use Docker and storage is tight.
- Clean cache directories to avoid stale dependencies.
- Update GitLab Runner deliberately; record the version bump in your inventory.
Conclusion
You now have a small, safe GitLab Runner lab you can trust for testing and learning:
- A dedicated non-root user and lab directory keep experiments isolated.
- The shell executor gives you the fastest path to local validation.
- The Docker executor adds clean toolchain isolation when needed.
- Clear verification steps, expected outputs, and logs make results easy to review.
- Known failure modes have short, deterministic fixes and rollback.
- A lightweight checklist keeps operations repeatable.
Next steps:
- Extend your local jobs with realistic build and test steps, keeping each change small and measurable.
- Pin versions for tools and images so re-runs are consistent.
- When comfortable, register a runner against a non-production project to test full pipeline flows, still following the same safety principles.
A narrow, measurable pilot that runs locally end-to-end builds confidence quickly and reduces rework as you scale your use of GitLab Runner.