GitLab Group-Level Variables and Shared Runners Across Multiple Projects
AI generated
CI/CD
.yml
GitLab · CI/CD · DevOps
Group-Level Variables and Shared Runners
managed cleanly across many projects

Anyone maintaining more than a handful of GitLab projects quickly discovers that keeping variables and runners in sync per project becomes a full-time job. Group-wide configuration solves exactly that problem.

17 min read Group Variables Shared Runner Group Runner Multi-Project

1. Why project-level variables break down at scale

In most organizations, CI/CD configuration grows organically: one project gets a deploy variable, the next one copies it, the third one drifts slightly because of a typo somewhere along the way. A year later, five different versions of the same API key variable exist across five different projects, and nobody can reliably say which one is still valid. This is not a hypothetical problem, it is the default state of any GitLab instance that has grown past ten or so active repositories.

Group-level variables fix this at the root by defining a value exactly once and inheriting it into every project within the group and its subgroups. A change made in one place immediately propagates to every pipeline that reads it, with no manual follow-up needed in each individual project. The effect is largest for values that rotate regularly, such as deploy tokens or registry credentials, where a forgotten project would otherwise silently fail with stale credentials.

2. Creating group variables and controlling visibility

Group variables are created under Settings, CI/CD, Variables at the group level, not inside an individual project. Each variable gets the same options as a project variable: Protected restricts visibility to protected branches and tags, Masked prevents the value from showing up in job logs, and Environment Scope lets the same variable name hold different values for different environments. Masked and Protected should be enabled without exception for anything that looks like a secret.

A common mistake is putting every variable of an organization at the top group level, even though only a subset of projects actually needs it. A tiered approach through subgroups works better: a general registry URL belongs at the top level, while a deploy key for one specific product line belongs only in the subgroup containing those products. This keeps each team's access surface small, and a compromised token in one team does not automatically pull every other team into the blast radius.


# .gitlab-ci.yml of a project inside the group
# DEPLOY_TOKEN and REGISTRY_URL are NOT defined in this file,
# they are managed at the group level under Settings > CI/CD > Variables.

stages:
  - build
  - deploy

deploy_staging:
  stage: deploy
  image: alpine:3.20
  script:
    - echo "Deploying to $REGISTRY_URL"
    - curl -H "Authorization: Bearer $DEPLOY_TOKEN" "$REGISTRY_URL/api/deploy"
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

3. Inheritance order: when project and group variables collide

GitLab resolves naming conflicts with a clear priority: a variable defined directly in a project overrides a same-named variable from the group, and a subgroup variable overrides one from a parent group above it. This enables targeted exceptions, for example when a single project needs to point at a different registry temporarily, without touching the shared group configuration at all.

That flexibility has a downside: when a project mysteriously uses the wrong configuration, the cause is often a long-forgotten project variable silently overriding a newer group variable. It pays off to check all three levels systematically during troubleshooting, from the top group through subgroups down to the project itself, rather than immediately suspecting a runner or network issue. A periodic audit of which project variables shadow a group variable reliably surfaces these leftovers.

4. Shared Runners: provided by the GitLab instance

Shared Runners are operated centrally by the GitLab instance, or by GitLab itself on GitLab.com, and are available to every project on that instance unless explicitly disabled. Their big advantage is zero management overhead: no infrastructure to run, patch, or scale yourself. For smaller teams or projects with moderate pipeline volume this is often the right choice, especially when there are no special hardware needs like GPUs or very large caches.

The downside shows up under heavy load or special requirements: Shared Runners are used by many projects at once, which can cause queueing delays when many other teams trigger pipelines against the same pool at the same time. On self-hosted GitLab instances, a Shared Runner also means every project on the instance can theoretically run jobs on it, which is often undesirable from a security standpoint for sensitive deploy pipelines.

5. Group Runners: the middle ground for organizations

A Group Runner is registered at the group level and is available exclusively to projects within that group and its subgroups. That makes it the natural choice for organizations managing several related projects, for example all repositories of one product team, who want to share resources while limiting the access surface to unrelated projects on the same instance. A Group Runner with special hardware, such as extra RAM for build-heavy jobs, only needs to be set up once instead of per project.

Registration happens under Settings, CI/CD, Runners at the group level, where a registration token is generated and then used during runner setup on the target machine. It is important to tag the runner with descriptive labels, such as docker or high-memory, so that jobs in .gitlab-ci.yml can route to it deliberately instead of accidentally landing on a mis-sized runner. Without tags, GitLab falls back to default matching, which quickly produces surprising results in mixed environments.


build_backend:
  stage: build
  tags:
    - docker
    - high-memory
  image: node:20-alpine
  script:
    - npm ci
    - npm run build

6. Project Runners: isolation for especially sensitive pipelines

A Project Runner is tied to exactly one project and is typically used where isolation matters more than shared resources: production-facing deploy pipelines, projects with strict compliance requirements, or jobs that need access to sensitive internal networks that no other project in the organization should reach. Since only that one project can run jobs on it, the attack surface is minimal, which often justifies the extra management overhead.

In practice a mixed strategy works well: the bulk of build and test jobs run on Group Runners, while only the actual deploy stage is offloaded to a dedicated Project Runner with access to the production environment. This split drastically reduces the number of machines with production access without slowing down the rest of the pipeline, since those steps still benefit from the parallelism of shared runners.

7. Targeting runners deliberately with tags and rules

Once multiple runner tiers exist side by side, every pipeline needs to explicitly control which job lands on which runner. This happens through the tags directive on a job: GitLab automatically picks a matching runner among all available ones that carry every specified tag. A job without tags can in principle run on any runner that has not disabled untagged jobs, which in mixed environments of Shared, Group, and Project Runners can lead to unpredictable behavior if it is not clearly documented which runner carries which tags.

For production-critical jobs, combining tags with rules is worthwhile to guarantee that a deploy job really only runs on the intended Project Runner and only on the protected main branch. This double safeguard prevents a misconfigured feature-branch job from ending up on a runner with production access, even if someone accidentally copies the tag but forgets the rules condition.


deploy_production:
  stage: deploy
  tags:
    - project-runner-prod
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
      when: manual
  environment:
    name: production
    url: https://example.com

8. Variable hygiene as the project count grows

As the number of projects grows, consistent naming and documentation of variables becomes increasingly important. A proven pattern is a purpose-based prefix, such as DEPLOY_ for everything related to deployments and REGISTRY_ for container registry credentials, combined with a short note in the Description field GitLab offers for every variable. Without this discipline, variables accumulate over months whose original purpose nobody can reconstruct anymore, and out of caution nobody deletes them either.

A regular review cycle, for example quarterly, that walks through all group and project variables prevents this buildup of dead weight. It is especially worth asking whether a variable is still referenced by an active pipeline, which can be checked simply by searching for the variable name across every .gitlab-ci.yml file in the group. Variables that have not appeared in any file for months can usually be removed safely.

9. Practical example: combining three runner tiers in one organization

A concrete setup for an organization with ten projects might look like this: at the top group level sit general variables like REGISTRY_URL and a Shared Runner for fast, non-critical lint and test jobs. At the subgroup level, say for a specific product team, a Group Runner with more resources handles build jobs, along with a group variable for the team's staging access. Only the actual production deploy jobs of the most important projects finally run on a dedicated Project Runner with its own project variable for the production token.

This three-tier combination ensures that the bulk of pipeline work is handled by shared, efficiently utilized runners, while only the narrow, security-critical path gets isolated resources. The table below summarizes when each runner tier is the right choice and what to watch for in its variable configuration.

Tier Visibility Typical use Variable recommendation
Shared Runner All projects on the instance Lint, unit tests, non-critical jobs Non-sensitive values only, no deploy token
Group Runner Group and subgroups Build jobs, one team's staging deploys Group variables with Protected for protected branches
Project Runner A single project Production deploys, sensitive networks Project variables, Masked and Protected mandatory
Variable review Quarterly Check all tiers Remove unused variables, keep Description filled in

Mironsoft

CI/CD pipelines, zero-downtime deployments and release automation

Deployments that run without downtime and without the nail-biting?

We review existing GitLab pipelines for fragile deployment steps and missing safeguards, then build a release process with zero-downtime deployments, automated checks and a rollback you can actually trust in an emergency.

Pipeline Review

Checking an existing .gitlab-ci.yml for fragility, missing stages and security gaps.

Zero-Downtime Deployment

Building symlink releases, health checks and rollback strategies for Magento stores.

CI/CD Automation

Connecting tests, security scans and deployments into one reliable pipeline.

10. Summary

Group-Level Variables and Runners: Key Takeaways

Group Variables

Defined once at the group level, automatically inherited into every project and subgroup.

Priority

Project variable beats subgroup beats top group when names collide.

Runner choice

Shared for bulk work, Group for teams, Project for isolated production deploys.

Hygiene

Prefixes, the Description field, and regular reviews prevent dead variable clutter.

11. FAQ: Group-Level Variables and Runners: Key Takeaways

1What is the difference between group variables and instance variables?
Instance variables apply to the entire GitLab installation and are managed by administrators, while group variables apply only to a specific group and its subgroups and can be managed by group maintainers themselves without needing admin rights on the whole instance.
2Can I override a group variable in a single project?
Yes, a variable defined directly in the project with the same name always takes priority over a same-named group variable. This allows targeted exceptions for individual projects without changing the shared configuration.
3How many Group Runners can a group have at once?
GitLab itself imposes no fixed limit, in practice only available infrastructure is the constraint. Multiple Group Runners with different tags for different workload types is a common pattern.
4Should I use Shared Runners for production deploys?
This is generally discouraged, since Shared Runners are used by many projects on the instance and thus present a larger attack surface for sensitive production secrets. Dedicated Project or Group Runners are the safer choice for deploys with production access.
5What happens if a job specifies no tags?
A job without a tags directive can run on any runner that has not explicitly disabled untagged jobs. In mixed environments with many specialized runners this often leads to unpredictable runner assignment and should be avoided.
6How do I prevent sensitive variables from appearing in job logs?
The Masked option on a variable definition replaces its value with asterisks in the job log automatically, provided the value meets certain formatting rules such as containing no line breaks. In addition, the full variable value should never be echoed out explicitly.
7Can a Group Runner also be used by subgroups?
Yes, a Group Runner registered on a group is by default also available to projects in that group's subgroups, unless access has been explicitly restricted.
8How do I migrate existing project variables to group variables?
The simplest approach is to recreate the values manually at the group level and then remove the identical project variables so inheritance takes effect. An automated script against the GitLab API can speed this process up across many projects.
9What is Environment Scope on a variable?
Environment Scope allows the same variable name to be defined multiple times with different values for different deploy environments, for example different DATABASE_URL values for staging and production, without needing to rename the variable in code.
10Is a dedicated runner worth it for a single small project?
Usually not. For small projects with low pipeline volume, a Shared or Group Runner is typically sufficient, and the extra management overhead of a dedicated Project Runner only pays off with special security or performance requirements.