Operations 8 min read

How to Run Both Shell and Docker Executors on a Single GitLab Runner Host

Learn how to configure a single GitLab Runner on an Ubuntu server to handle both shell and Docker executors by registering multiple runner instances, assigning distinct tags, and using .gitlab-ci.yml job tags for precise task scheduling, complete with step‑by‑step commands and examples.

Ops Development & AI Practice
Ops Development & AI Practice
Ops Development & AI Practice
How to Run Both Shell and Docker Executors on a Single GitLab Runner Host

Introduction

When building automated CI/CD pipelines, you often need heterogeneous tasks: some jobs must run directly on the host (e.g., using the shell executor) while others require an isolated environment such as Docker. This guide explains how a single machine with GitLab Runner installed can support both executors simultaneously.

Understanding the GitLab Runner Configuration Model

The core of GitLab Runner’s behavior is defined in config.toml. The file contains a global section and one or more [[runners]] blocks. Each [[runners]] block represents an independent Runner instance registered with GitLab. The gitlab-runner service reads all blocks and starts a separate logical Runner for each.

Common Misconception: One Runner, One Executor

A Runner registered with gitlab-runner register can have only one executor value. For example, a shell‑based Runner looks like this:

[[runners]]
  name = "shell-runner-on-ubuntu"
  url = "https://gitlab.com/"
  token = "your-token-here"
  executor = "shell"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]

Adding a second executor = "docker" line to the same block is invalid because the executor defines the entire job execution model and cannot be mixed.

Solution: Register Multiple Runners, Each with a Single Executor

The correct approach is to register separate Runner instances on the same host, each with its own executor and distinct tags.

Step 1 – Register a Shell Executor

Run the registration command with --executor "shell" and assign tags such as shell,ubuntu,host-access:

sudo gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.com/" \
  --registration-token "YOUR_REGISTRATION_TOKEN" \
  --executor "shell" \
  --description "Shell Runner for Host Operations" \
  --tag-list "shell,ubuntu,host-access"

Step 2 – Register a Docker Executor

Run the registration command again, this time with --executor "docker", a Docker image, and tags like docker,ubuntu,isolated:

sudo gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.com/" \
  --registration-token "YOUR_REGISTRATION_TOKEN" \
  --executor "docker" \
  --description "Docker Runner for Isolated Builds" \
  --docker-image "alpine:latest" \
  --tag-list "docker,ubuntu,isolated"

After both commands, /etc/gitlab-runner/config.toml will contain two [[runners]] blocks—one with executor = "shell" and another with executor = "docker". The GitLab Runner service will manage both concurrently.

Practical .gitlab-ci.yml Example

Use job‑level tags to direct each job to the appropriate Runner:

stages:
  - build
  - deploy

build_app:
  stage: build
  image: node:18-alpine  # Docker executor image
  tags:
    - docker  # Must run on Docker Runner
  script:
    - echo "Building the application inside a Docker container..."
    - npm install
    - npm run build
  artifacts:
    paths:
      - dist/

deploy_to_prod:
  stage: deploy
  tags:
    - shell  # Must run on Shell Runner
  script:
    - echo "Deploying to production server..."
    - cp -r dist/* /var/www/html/
    - echo "Deployment complete."

When the pipeline triggers, GitLab CI matches the docker tag to the Docker Runner and the shell tag to the Shell Runner, ensuring each job runs in the intended environment.

UML Visualization

The following diagram visualizes how tags act as traffic signs, directing jobs to the correct Runner:

UML activity diagram of Runner scheduling
UML activity diagram of Runner scheduling

Conclusion

A single Ubuntu host with GitLab Runner can simultaneously support both shell and docker executors by registering multiple, single‑purpose Runner instances and leveraging the tag mechanism for precise job scheduling. This pattern solves environment diversity, improves CI/CD clarity, and scales to additional architectures or specialized hardware.

DockerCI/CDDevOpsGitLab RunnerRunner ConfigurationShell Executor
Ops Development & AI Practice
Written by

Ops Development & AI Practice

DevSecOps engineer sharing experiences and insights on AI, Web3, and Claude code development. Aims to help solve technical challenges, improve development efficiency, and grow through community interaction. Feel free to comment and discuss.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.