Named Runners
Named runners let you deploy isolated runner containers, each with its own config.toml. Jobs are dispatched to a named runner when any of their CI tags match the runner name.
Quick start#
Define a named runner in your config and tag jobs to use it:
# .glciconfig.toml
[runners.gpu]
config_template = """
[[runners]]
[runners.docker]
privileged = true
gpus = "all"
memory = "16g"
"""
# .gitlab-ci.yml
train-model:
tags: [gpu]
image: nvidia/cuda:12.0-devel
script:
- python train.py
When you run glci run, the train-model job is routed to the gpu runner container. All other jobs run on the default shared runner.
How it works#
The daemon’s RunnerManager coordinates three tiers of runner containers:
| Tier | Container name | When created |
|---|---|---|
| Shared | glci-runner-{suffix} | Always (default for all projects) |
| Per-project | glci-runner-{suffix}-{project} | When a project sets [runner] config_template |
| Named | glci-runner-{suffix}-{project}-{name} | When [runners.<name>] is defined |
Job dispatch#
- For each job, glci checks if any of its tags match a named runner name
- First tag match wins — the job is assigned to that named runner
- Unmatched jobs go to the default runner (shared or per-project)
- Named runners have
run_untagged=false— they only receive explicitly tagged jobs
Config templates#
Each named runner accepts a config_template (inline Go template) or config_template_file (path to a template file) that generates its config.toml.
Inline template#
[runners.gpu]
config_template = """
[[runners]]
[runners.docker]
gpus = "all"
privileged = true
volumes = ["/cache", "/usr/local/nvidia:/usr/local/nvidia:ro"]
"""
External template file#
[runners.gpu]
config_template_file = "runners/gpu-config.toml.tpl"
The template file uses the same Go text/template syntax. See Runner config templates for the full list of template variables.
Default runner templates#
The default runner ([runner]) can also use config_template to customize its config.toml. This is useful when you need to change runner-level settings globally without defining named runners:
# .glciconfig.toml
[runner]
config_template = """
[[runners]]
[runners.docker]
privileged = true
network_mode = "host"
shm_size = 2147483648
"""
When the default runner has a config_template, it gets its own per-project container (separate from the shared runner).
Multiple named runners#
You can define as many named runners as needed:
# .glciconfig.toml
[runners.gpu]
config_template = """
[[runners]]
[runners.docker]
gpus = "all"
privileged = true
"""
[runners.arm]
config_template = """
[[runners]]
[runners.docker]
platform = "linux/arm64"
"""
[runners.shell]
config_template = """
[[runners]]
executor = "shell"
"""
# .gitlab-ci.yml
train:
tags: [gpu]
script: python train.py
build-arm:
tags: [arm]
script: make build
deploy:
tags: [shell]
script: ./deploy.sh
lint:
# no tags — runs on the default runner
script: make lint
Runner name rules#
Runner names must match [a-zA-Z0-9][a-zA-Z0-9_-]*:
- Start with a letter or digit
- Can contain letters, digits, hyphens, underscores
- Names are case-sensitive
Docker host per runner#
Named runners can target different Docker daemons via docker_host (raw endpoint) or docker_context (Docker context name). The runner container and all job containers it spawns are created on the specified daemon.
# .glciconfig.toml
# Using docker_host (raw endpoint URL)
[runners.colima]
docker_host = "unix:///Users/me/.colima/default/docker.sock"
# Using docker_context (Docker context name — respects TLS config)
[runners.arch]
docker_context = "arch"
[runners.gpu]
docker_host = "ssh://gpu-host"
config_template = '''
[[runners]]
[runners.docker]
gpus = "all"
'''
# TCP with TLS client certificates
[runners.secure]
docker_host = "tcp://build-host:2376"
tls_cert_path = "/path/to/certs" # directory with ca.pem, cert.pem, key.pem
tls_verify = true # sets DOCKER_TLS_VERIFY=1
docker_host supports schemes: unix://, tcp://, ssh://, fd://.
For tcp:// endpoints that require TLS, set tls_cert_path to the directory containing ca.pem, cert.pem, and key.pem, and tls_verify = true to enable verification. Each runner can use different certificates, allowing connections to multiple TLS-secured Docker daemons.
docker_context takes a Docker context name (as shown by docker context ls). This is simpler when the context is already configured in Docker and respects its TLS settings.
If both are set, docker_host takes precedence.
Windows Docker daemons#
Named runners can target Windows Docker daemons. glci auto-detects whether the remote daemon runs Windows by probing docker info --format '{{.OSType}}' and uses the correct named pipe (//./pipe/docker_engine) for Docker socket mounts in runner containers.
For manual override, set container_socket:
[runners.windows]
docker_host = "tcp://windows-host:2375"
container_socket = "//./pipe/docker_engine"
This is useful as a fallback when auto-detection is unavailable (e.g. restricted docker info access).
When a Windows daemon is on a different host (requiring a relay proxy), glci automatically uses the Windows variant of the glci image (glci:local-windows or the registry image with -windows suffix). Build it locally with make docker-windows.
Remote runner behavior#
When a runner targets a remote Docker daemon, glci automatically:
- Pulls the
gitlab-runnerandglci:localimages on the remote host - Creates a dedicated bus network on the remote daemon (
glci-net-{pipeline}-{runnerName}) - If the remote daemon is on a different machine (not localhost), deploys a relay proxy container that bridges mock server requests back through
docker exec - If the remote daemon is on the same machine (e.g. a different Colima instance), uses
host-gatewayextra_hosts directly
Jobs tagged to a remote runner execute entirely on that daemon — the runner container, job containers, and service sidecars all live on the remote host. Untagged jobs continue to run on the default (local) Docker daemon. Artifacts, cache, and git data all flow through the relay proxy transparently.
See also Docker & Network for the full network topology diagram and How It Works for the relay proxy architecture.
Lifecycle#
- Runner containers are created lazily on first use
- Containers are reused across pipeline runs (persistent)
- Containers are stopped when the daemon shuts down
- If a container is manually removed, the daemon recreates it on next use