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:

TierContainer nameWhen created
Sharedglci-runner-{suffix}Always (default for all projects)
Per-projectglci-runner-{suffix}-{project}When a project sets [runner] config_template
Namedglci-runner-{suffix}-{project}-{name}When [runners.<name>] is defined

Job dispatch#

  1. For each job, glci checks if any of its tags match a named runner name
  2. First tag match wins — the job is assigned to that named runner
  3. Unmatched jobs go to the default runner (shared or per-project)
  4. 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_-]*:

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:

graph LR subgraph your_machine["Your machine"] D["glci daemon"] M["glci-mock
(mock server)"] end subgraph remote_host["Remote Docker host"] R["Runner
(gitlab-runner)"] P["Relay proxy
(glci-proxy-*)"] J["Job containers"] end D -.->|"docker exec -i
(muxproto)"| M D -.->|"docker exec -i
(muxproto)"| P R -->|"HTTP via Docker DNS
(glci-mock:39741)"| P R -->|"Docker executor"| J J -->|"HTTP via extra_hosts
(glci-mock)"| P P -.->|"relay → daemon → mock"| D style your_machine fill:#ecfdf5,stroke:#10b981,stroke-width:2px style remote_host fill:#fef3c7,stroke:#f59e0b,stroke-width:2px

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#

Esc