GitLab Runner on Docker: Complete Self-Hosted CI/CD Setup Guide

Gitlab

Related Posts

All discussions about programming technologies and other technologies
As we move into 2025, the programming world is set ...
Docker
Have you ever had trouble running an application on your ...
odoo erp
Le's talk about Odoo deployment. It will be easy because ...

Introduction

Running CI/CD pipelines is essential for modern software development, but relying on shared GitLab runners can be limiting. You might need to deploy to local Docker services, access internal networks, or handle builds that exceed shared runner quotas.

Self-hosting a GitLab Runner in Docker solves all of this.

In this guide, we’ll walk through setting up a GitLab Runner inside a Docker container, connecting it to your GitLab projects, and configuring it to run production-ready CI/CD pipelines. Whether you’re using GitLab Cloud or self-hosted GitLab, this setup works seamlessly.

By the end, you’ll have a fully functional CI/CD runner that picks up jobs directly from GitLab and executes them on your own infrastructure.

What Is GitLab Runner and Why Self-Host?

GitLab Runner is the application that executes CI/CD pipeline jobs defined in your .gitlab-ci.yml files. By default, GitLab offers shared runners, but self-hosting gives you:

✅ Control over environment — Install exact dependencies your project needs
✅ Local Docker access — Deploy containers, restart services, build images
✅ Internal network access — Reach databases, APIs, and services on your private network
✅ No quota limits — Run unlimited jobs (hardware permitting)
✅ Cost savings — No per-minute billing on shared runners

Prerequisites: What You’ll Need

Before starting, ensure you have:

  • Docker Desktop installed and running (macOS/Windows) or Docker Engine (Linux)
  • GitLab account — either gitlab.com (cloud) or self-hosted GitLab instance
  • A GitLab project you want to connect to the runner
  • Mac or Linux machine (this guide uses macOS; Linux paths are nearly identical)
  • Basic Docker knowledge — familiarity with Docker Compose is helpful

Step 1: Project Structure Setup

Start by creating a dedicated directory for your GitLab Runner configuration:

gitlab-runner/
├── Dockerfile
├── entrypoint.sh
├── docker-compose.yml
└── config/
    └── config.toml        # auto-generated after registration

This structure keeps everything organized and makes it easy to manage your runner configuration.

Step 2: Custom Dockerfile for GitLab Runner

The default GitLab Runner image is minimal. We’ll extend it with Node.js 20 and Docker CLI so your jobs can build images, install dependencies, and control containers.

Create a Dockerfile in your gitlab-runner/ directory:

FROM gitlab/gitlab-runner:latest

USER root

# Install Node.js 20
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs \
    && npm install -g npm@latest

# Install Docker CLI
RUN apt-get update && \
    apt-get install -y ca-certificates curl gnupg && \
    install -m 0755 -d /etc/apt/keyrings && \
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
    chmod a+r /etc/apt/keyrings/docker.gpg && \
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \
    apt-get update && \
    apt-get install -y docker-ce-cli docker-compose-plugin

# Copy and enable entrypoint
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
CMD ["gitlab-runner", "run", "--user=root", "--working-directory=/home/gitlab-runner"]

Pro tip: If your project doesn’t use Node.js, you can remove that installation section to reduce image size.

Step 3: Create the Entrypoint Script

The entrypoint script runs every time the container starts. It handles two critical tasks:

  1. Fix Docker socket permissions — Docker Desktop resets socket permissions on restart
  2. Trust all Git directories — Prevent ownership errors when running as root

Create entrypoint.sh:

#!/bin/bash
chmod 666 /var/run/docker.sock
git config --global --add safe.directory '*'
exec "$@"

This small script saves you from common permission and Git authentication errors.

Step 4: Docker Compose Configuration

Now create docker-compose.yml to orchestrate your runner container:

services:
  gitlab-runner:
    build: .                         # uses local Dockerfile
    container_name: gitlab-runner
    restart: always
    user: root                       # run as root to avoid su auth issues
    volumes:
      - ./config:/home/gitlab-runner/.gitlab-runner   # persist config.toml
      - /var/run/docker.sock:/var/run/docker.sock      # Docker socket access
      - /Users/youruser/.ssh:/root/.ssh:ro             # SSH key for git pull
      - /path/to/your/project:/app/project             # mount project folder

Important volume configuration notes:

  • Replace /Users/youruser with your actual home directory path
  • Add one volume mount per project the runner needs to access
  • On Linux, the Docker socket path is identical: /var/run/docker.sock
  • SSH mount allows passwordless Git operations

To build and start the runner:

docker compose up -d --build

Step 5: Register Runner with GitLab

Get Your Registration Token

  1. Go to your GitLab project → Settings → CI/CD → Runners
  2. Click New project runner
  3. Set a descriptive tag (e.g., my-server, production, docker-build)
  4. Click Create runner
  5. Copy the registration token (starts with glrt-…)

Register Inside the Container

docker exec -it gitlab-runner gitlab-runner register

Answer the interactive prompts:

GitLab URLhttps://gitlab.com (or your self-hosted URL)
TokenPaste your glrt-… token
Descriptionmy-server-runner
Tagsmy-server
Executorshell

Add GitLab to Known Hosts (First Time Only)

ssh-keyscan gitlab.com >> ~/.ssh/known_hosts

Important: Run this on your host machine, not inside the container—your SSH directory is volume-mounted.

Verify Registration

docker exec -it gitlab-runner gitlab-runner list

The runner should now appear as Online in your GitLab project’s Settings → CI/CD → Runners page.

Step 6: Understanding config.toml

After registration, GitLab Runner auto-generates ./config/config.toml. Here are the key settings:

concurrent = 1              # max parallel jobs (start low on limited hardware)
check_interval = 0
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "my-server-runner"
  url = "https://gitlab.com"
  id = 12345678
  token = "glrt-xxxxxxxxxxxx"
  executor = "shell"
  request_concurrency = 2   # suppresses long-polling warning
  [runners.cache]
    MaxUploadedArchiveSize = 0

Key parameters explained:

  • concurrent — Maximum parallel jobs. Keep at 1 on low-RAM machines; increase only if your hardware supports it
  • request_concurrency — Suppresses long-polling warning messages in logs
  • Each registered project adds a new [[runners]] block

Creating Your First CI/CD Pipeline

Now let’s create a .gitlab-ci.yml file for a typical Node.js project with Docker deployment:

stages:
  - pull
  - install
  - deploy

variables:
  APP_DIR: /app/your-project   # must match volume mount in docker-compose.yml

# Only run on development branch
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == "development"

pull:
  stage: pull
  tags:
    - my-server              # must match runner tag
  script:
    - cd $APP_DIR
    - git pull origin development
  only:
    - development

install:
  stage: install
  tags:
    - my-server
  script:
    - cd $APP_DIR
    - npm install
  only:
    - development

deploy:
  stage: deploy
  tags:
    - my-server
  script:
    - cd $APP_DIR
    - docker compose up -d --build your-service-name
  only:
    - development
  environment:
    name: development

Before pushing to GitLab, verify:

✓ APP_DIR matches your runner’s docker-compose.yml volume mount
✓ Tag (my-server) matches your registered runner tag
✓ Service name matches your project’s docker-compose.yml (not container_name)

Conclusion

You now have a self-hosted GitLab Runner that executes CI/CD pipelines on your own infrastructure. This setup gives you complete control over:

  • Environment dependencies and versions
  • Docker operations and image builds
  • Access to internal networks and services
  • Job execution without quota limitations

Whether you’re deploying Odoo modules, Node.js applications, or complex Docker stacks, this runner provides the flexibility and performance your development workflow needs.

Next steps:

  1. Customize .gitlab-ci.yml for your specific project
  2. Test with a small pipeline first
  3. Scale up with additional runners if needed
  4. Monitor performance and adjust concurrent settings

Have questions about your setup? The official GitLab Runner documentation has additional executor types and advanced configurations. Other knowledge you might be need, visit Programmermumet.

Scroll to Top