BLOG · GUIDE ·

GPU CI runners for GitLab and GitHub Actions: one card or MIG slice per job

Eurokommerz, Vienna, since 2006: Private AI/ML · IT Managed Services · Enterprise Training · AI Hardware & Software

IN BRIEF
  • GitLab Runner passes GPUs to jobs with the gpus and service_gpus settings of the Docker executor, which take the Docker CLI format; the Kubernetes executor requests nvidia.com/gpu through a pod_spec patch behind the FF_USE_ADVANCED_POD_SPEC_CONFIGURATION feature flag
  • GitHub Actions runs GPU jobs on self-hosted runners; GitHub recommends ephemeral runners that de-register after one job and warns that self-hosted runners "should almost never be used for public repositories"
  • One registered runner per card with limit = 1 keeps jobs on separate GPUs; with MIG an RTX PRO 6000 Blackwell Server Edition serves up to four jobs of 24 GB and an RTX PRO 4500 Server Edition two of 16 GB
  • A CI test on an L4 (compute capability 8.9) does not exercise the sm_120 code paths that RTX PRO Blackwell cards (12.0) run in production
  • Model weights belong in a read-only cache mounted into the job with HF_HUB_OFFLINE=1, not in a fresh download per job: gpt-oss-120b alone is about 65 GB of safetensors

Supplied by Eurokommerz: RTX PRO 6000 and H200 NVL  Check price and availability →

What a GPU runner is and when a team needs one

A GPU CI runner is a self-hosted GitLab Runner or GitHub Actions runner on a machine with an NVIDIA card, the NVIDIA driver and the NVIDIA Container Toolkit, configured so that each CI job gets one GPU or one MIG slice. It runs the jobs that a normal CPU runner cannot: CUDA unit tests, kernels compiled for the cards you deploy on, model evaluations against a fixed test set, and smoke tests of inference containers before they reach production.

Teams usually reach this point in one of three ways. A CUDA or PyTorch code base gains tests that fail on CPU only, an ML team wants every merge request to rerun its evaluation on the same hardware, or a platform team builds inference images and wants to start the container with a real model before it is pushed. In all three cases the job needs exclusive access to known GPU memory for minutes, and the runner has to be configured so that two jobs never land on the same card by accident.

This article covers the runner side: the settings GitLab and GitHub document, isolation per job, caches, security and the card. The general mechanics of handing GPUs to containers are in our guide to Docker GPU assignment without Kubernetes, and sharing GPUs between people rather than pipelines is the subject of GPU chargeback and quotas for a shared GPU platform.

GitLab Runner with the Docker executor and NVIDIA GPUs

GitLab's page "Using Graphical Processing Units (GPUs)" (GitLab Runner documentation, docs version 19.6, read 25 October 2026) states that GPU support was introduced in GitLab Runner 13.9 and lists the prerequisites for the Docker executor: the NVIDIA driver and the NVIDIA Container Toolkit on the host. Two settings in the [runners.docker] section do the work: gpus for the job container and service_gpus for service containers. GitLab's example sets both to "all". The advanced configuration reference describes both as "GPU devices for Docker container. Uses the same format as the docker CLI."

That format is Docker's --gpus option. Docker's GPU access page shows --gpus all, --gpus device=0 for one card by index, --gpus device=GPU-<uuid> for one card by UUID, and '"device=0,2"' for two cards, with the note that "the device value must be quoted because it contains a comma." The shell executor needs no runner configuration at all, according to GitLab, but it gives every job the host and all its GPUs, and GitLab's security page advises using it "only for running trusted builds".

The setting gpus = "all" is right for a host that runs one GPU job at a time. On a host with four cards and four parallel jobs it is wrong, because every job sees every card. GitLab offers two limits: the global concurrent value, which "limits how many jobs can run concurrently, across all registered runners", and the per-runner limit, which caps the jobs "handled concurrently by this registered runner". Registering one runner entry per card, each with its own device and a limit of one, gives each job one card:

RUNNER ENTRYGPUS SETTINGLIMIT
gpu-0gpus = "device=0"1
gpu-1gpus = "device=1"1
gpu-2gpus = "device=2"1
gpu-3gpus = "device=3"1

our example layout for a four-card host, applying the Docker CLI format that GitLab Runner documents for gpus (docs 19.6) and the device syntax of Docker's GPU access page; global concurrent set to 4. All read 25 October 2026.

GitLab suggests validating access by running nvidia-smi at the start of the job script: it lists the devices when the GPU is passed through and fails with a driver communication error when it is not. Keep that line in the first job of every GPU pipeline. GitLab also records one limitation: with Podman instead of Docker the GPU is not detected (GitLab issue 39095).

GitLab Runner on Kubernetes

With the Kubernetes executor, the GPU is a resource request. GitLab's page names two prerequisites: a node selector that targets GPU nodes and the feature flag FF_USE_ADVANCED_POD_SPEC_CONFIGURATION. The documented example adds a [[runners.kubernetes.pod_spec]] entry with a strategic merge patch that sets nvidia.com/gpu: 1 under both requests and limits of the build container. GitLab notes that it has tested this on a managed public-cloud Kubernetes service with GPU instances.

The resource name comes from NVIDIA's device plugin, which the GPU Operator installs. With MIG in the operator's mixed strategy, each profile becomes its own resource, such as nvidia.com/mig-1g.24gb for a quarter of an RTX PRO 6000, as our article on GPU sharing in Kubernetes with MIG, time-slicing and MPS explains. A CI job then requests that name in the same patch instead of a whole GPU. Time-slicing advertises one card several times without memory isolation, which defeats the point of a reproducible test, so for CI we would use whole cards or MIG.

Self-hosted GPU runners for GitHub Actions

GitHub-hosted runners do not see your hardware, so GPU jobs on your own cards run on self-hosted runners. GitHub's documentation (read 25 October 2026) says such a runner "can be physical, virtual, in a container, on-premises, or in a cloud", that the host "must be able to make outbound HTTPS connections over port 443", and that a job which remains queued for more than 24 h fails. Jobs reach a runner by matching their runs-on labels, so a label such as gpu-blackwell sends a job to the cards it was written for.

For isolation GitHub is explicit. It recommends "autoscaling with ephemeral self-hosted runners": an ephemeral runner is de-registered automatically "after it has processed one job", and just-in-time runners are created for one job through the REST API. GitHub's security guidance adds that, even with runners that remove themselves, nothing ensures that a self-hosted runner only runs one job, and that secrets passed as command-line arguments "can be seen by another job running on the same runner"; just-in-time runners "perform at most one job before being automatically removed".

On Kubernetes, Actions Runner Controller (ARC) is "the reference implementation of GitHub's scale set APIs". GitHub's ARC page states "you can fully customize the PodSpec of the runner pod", so a GPU is requested in the runner container's resources like in any other pod. Two details matter for GPUs. In Docker-in-Docker mode "the Docker-in-Docker container requires privileged mode", which is the setting GitLab's security page calls insecure. In kubernetes mode, ARC starts container jobs in a new pod in the same namespace, so check that the GPU request reaches the pod that runs the job step and not only the runner pod. The minRunners and maxRunners values cap how many GPU jobs run at once; with both unset ARC scales to the number of assigned jobs.

We supply the L4, the RTX PRO 4500 Server Edition and the RTX PRO 6000 Server Edition, alone or in servers built to order; send us your pipelines and how many GPU jobs run at once, and our team replies with a runner host layout.

One GPU or one MIG slice per job

Most CI jobs need far less than a full 96 GB card. MIG cuts a card into instances with their own memory and compute, and NVIDIA's MIG user guide (updated 13 October 2026) shows how a container selects one: NVIDIA_VISIBLE_DEVICES with a MIG UUID, or Docker's --gpus '"device=0:0"' for the first MIG device on GPU 0, with "only one MIG device as chosen" visible to the container. The same value can go into a GitLab runner entry's gpus setting, one entry per instance. Run nvidia-smi -L in the job to confirm which instance it received.

CARDMIG PER CARDMEMORY PER JOB
L4no MIG24 GB, whole card
RTX PRO 4000 Blackwellno MIG24 GB, whole card
RTX PRO 4500 Server Edition2 instances16 GB
RTX PRO 6000 Server Editionup to 4 instances24 GB (1g.24gb)
H200 NVLup to 7 instances18 GB (1g.18gb)

NVIDIA MIG user guide (October 2026) as summarised in our RTX PRO 6000 MIG profiles and H200 NVL MIG profiles articles; NVIDIA datasheets for memory sizes.

Three limits apply. A job that tests multi-GPU code, such as tensor parallelism across two cards, needs whole cards, because a container sees one MIG device. MIG on the RTX PRO 6000 needs Linux, driver 575.51.03 or later and the card in compute display mode, steps our MIG setup guide walks through. And the MIG layout can only be changed while no job runs on the card, so pick one layout for the runner host and keep it.

Security for merge requests and pull requests

A CI runner executes code from whoever can push or open a merge request. GitLab's security page puts it plainly: "Any user that has the Developer role for the project's repository could compromise the security" of a runner, and "GitLab Runner is designed to run user-controlled scripts". GitHub writes that self-hosted runners "should almost never be used for public repositories on GitHub", because "any user can open pull requests against the repository and compromise the environment".

The documented measures for a GPU runner:

  • Docker executor in non-privileged mode, which GitLab considers safe, and no privileged = true or --pid=host on the GPU runner. GitLab writes that privileged mode is "effectively disabling all the container's security mechanisms".
  • No if-not-present pull policy on runners shared by projects with different access levels, which GitLab rules out because a cached image becomes a way to run code in other users' pipelines.
  • Ephemeral or just-in-time runners on GitHub, and on GitHub separate runner sets per trust level, restricted to the repositories allowed to use them.
  • A separate network segment for the runner host, which GitLab suggests "to reduce the attack surface if a job is malicious".
  • No production model API keys or registry write tokens on the GPU runner; jobs that publish images run on protected branches, as GitLab recommends for privileged work.

The driver and Container Toolkit carry their own CVEs. The fixed versions per branch are in our article on securing a GPU server; a CI host that runs code from many people is the first machine to patch.

Model weights and caches between jobs

An evaluation job that downloads its model from the Hub every time is slow and fragile. The Files page of gpt-oss-120b on Hugging Face (read 25 October 2026) lists 15 safetensors files totalling about 65 GB in the root, and 196 GB for the whole repository, which also holds original/ and metal/ folders. At 10 Gbit/s the root files alone take at least 52 seconds by our estimate, and every job that downloads them adds to the requests counted against the Hub's rate limits.

Hugging Face's client libraries read their cache location from HF_HUB_CACHE, which defaults to $HF_HOME/hub, and with HF_HUB_OFFLINE set "no HTTP calls will be made to the Hugging Face Hub" and only cached files are used (huggingface_hub environment variable reference, read 25 October 2026). On a GitLab runner, the volumes setting mounts a host directory with Docker -v syntax and environment sets variables for the job, so a cache filled once by a trusted job can be mounted read-only into every GPU job, for example /srv/hf:/hf:ro with HF_HUB_CACHE=/hf/hub and HF_HUB_OFFLINE=1. Read-only matters: a writable shared cache lets one job plant files that later jobs load.

How the cache gets filled, through a company proxy with revision pinning and checksums, is the subject of our article on Hugging Face models via Artifactory or a proxy. Disk sizes for model stores are in NVMe for an LLM server.

Which card for which CI pipeline with a GPU

The card in the runner should match the card in production in one respect above all: its compute capability. NVIDIA's CUDA GPU list (read 25 October 2026) gives 8.9 for the L4 and L40S, 9.0 for the H200 and 12.0 for every RTX PRO Blackwell card, Server Edition and workstation. A test suite that passes on an L4 has not run a single sm_120 kernel, and builds that lack Blackwell code fail on the production card with the error our article on no kernel image on Blackwell (sm_120) covers.

CARDCOMPUTE CAP.MEMORYPOWERCI ROLE
L48.924 GB72 Wtests for Ada and L40S targets
RTX PRO 4000 Blackwell12.024 GB145 WBlackwell tests in a workstation
RTX PRO 4500 Server Edition12.032 GB165 WBlackwell tests, 2 MIG jobs
RTX PRO 6000 Server Edition12.096 GBup to 600 Weval of large models, 4 MIG jobs
H200 NVL9.0141 GBup to 600 Wtests for Hopper targets

NVIDIA CUDA GPUs page (compute capability) and NVIDIA datasheets (memory, power), read September and October 2026.

For a team whose production runs on RTX PRO 6000 Server Edition cards, a runner host with RTX PRO 4500 Server Edition cards tests the same architecture with two jobs per card at 165 W each, and one RTX PRO 6000 Server Edition in the same pool takes the evaluations that need a large model. A team deploying to L4 or L40S servers tests on an L4: low profile, 72 W, and its 24 GB are enough for most unit tests. The RTX PRO 4000 Blackwell is a single-slot workstation card, a good fit for a CI workstation under a desk in the lab rather than a rack server.

Evaluation of a 70B-class model is a different load. In FP8 its weights alone are 67.7 GiB by our sizing canon, so it needs a full RTX PRO 6000 Server Edition or an H200 NVL, not a MIG slice. If you have mixed targets, tell us the architectures you ship to and we propose a runner host with one card type per target.

What we supply

We supply the L4, the RTX PRO 4000, the RTX PRO 4500 Server Edition, the RTX PRO 6000 Server Edition and the H200 NVL as cards with manufacturer warranty, and build workstations and AI servers to order, assembled and tested before delivery, with the operating system, NVIDIA driver, CUDA and the container runtime installed on request. Quantities and lead time are confirmed in writing with the quote, on one EU contract and invoice.

FAQ

How do I use a GPU in GitLab CI?
Install the NVIDIA driver and the NVIDIA Container Toolkit on the runner host and set gpus (and service_gpus for services) in the [runners.docker] section of config.toml, for example to "all" or to "device=0". Start the job with nvidia-smi to confirm the GPU is visible. On Kubernetes, request nvidia.com/gpu through a pod_spec patch.
Can GitHub Actions use a GPU on a self-hosted runner?
Yes. Install the runner on a host with the NVIDIA driver and route jobs with a runs-on label. GitHub recommends ephemeral runners that handle one job and warns against self-hosted runners on public repositories, because anyone can open a pull request.
How do I give each CI job its own GPU?
Register one runner entry per card, each with its own gpus = "device=N" value and limit = 1, and set the global concurrent to the number of cards. With MIG, one entry per MIG instance gives each job a fixed slice of memory.
Which GPU is best for a CI runner?
The one with the same compute capability as production: an L4 (8.9) for Ada targets, an RTX PRO 4500 Server Edition (12.0) for RTX PRO Blackwell targets, with two MIG jobs per card. Evaluations of large models need an RTX PRO 6000 Server Edition or an H200 NVL.
Is it safe to run merge requests from external contributors on a GPU runner?
Not on a persistent runner. GitHub states that self-hosted runners can be "persistently compromised by untrusted code in a workflow". Use ephemeral runners, a non-privileged Docker executor, a separate network segment and no production secrets on the host.
How many CI jobs can one RTX PRO 6000 Server Edition run at once?
Up to four with MIG, each with a 24 GB instance (1g.24gb). Jobs that need more memory or several GPUs take the whole card.

We supply L4, RTX PRO 4500 Server Edition and RTX PRO 6000 Server Edition cards and build runner hosts with drivers and the container toolkit installed. Describe your task in a few sentences and our team will help. We reply within one business day.

Talk to an expert
Talk to an expert

We reply within one business day

By sending this form you agree that we process your details to answer your enquiry; see our privacy policy.

request@eurokommerz.at
Jordangasse 7, 1010 Vienna